diff --git a/Dockerfile.loong64 b/Dockerfile.loong64
index 8fdb38705..2de68d742 100644
--- a/Dockerfile.loong64
+++ b/Dockerfile.loong64
@@ -77,7 +77,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
     && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl \
     && pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl \
     && pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl \
-    && pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.0-cp39-cp39-linux_loongarch64.whl \
+    && pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl \
     && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
     && pip install -r requirements/requirements.txt
 
diff --git a/README.md b/README.md
index ca31cce08..71872e4a2 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,14 @@
 
 --------------------------
 
-JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
+JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
+
+- **SSH**: Linux / Unix / 网络设备 等;
+- **Windows**: Web 方式连接 / 原生 RDP 连接;
+- **数据库**: MySQL / Oracle / SQLServer / PostgreSQL 等;
+- **Kubernetes**: 支持连接到 K8s 集群中的 Pods;
+- **Web 站点**: 各类系统的 Web 管理后台;
+- **应用**: 通过 Remote App 连接各类应用。
 
 ## 产品特色
 
@@ -33,8 +40,6 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
 - **多云支持**: 一套系统,同时管理不同云上面的资产;
 - **多租户**: 一套系统,多个子公司或部门同时使用;
 - **云端存储**: 审计录像云端存储,永不丢失;
-- **多应用支持**: 全面支持各类资产,包括服务器、数据库、Windows RemoteApp、Kubernetes 等;
-- **安全可靠**: 被广泛使用、验证和信赖,连续 9 年的持续研发投入和产品更新升级。
 
 ## UI 展示
 
@@ -72,12 +77,13 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
 - [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
 - [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
 
-## 社区
+## 社区交流
 
-如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)
-或加入到我们的社区当中进行进一步交流沟通。
+如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
 
-### 微信交流群
+您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 及微信交流群当中进行交流沟通。
+
+**微信交流群**
 
 
 
@@ -107,7 +113,7 @@ JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.ju
 - 邮箱:support@fit2cloud.com
 - 电话:400-052-0755
 
-## 致谢
+## 致谢开源
 
 - [Apache Guacamole](https://guacamole.apache.org/): Web 页面连接 RDP、SSH、VNC 等协议资产,JumpServer Lion 组件使用到该项目;
 - [OmniDB](https://omnidb.org/): Web 页面连接使用数据库,JumpServer Web 数据库组件使用到该项目。
diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py
index b25dafb9f..6fb699721 100644
--- a/apps/accounts/api/account/account.py
+++ b/apps/accounts/api/account/account.py
@@ -32,6 +32,7 @@ class AccountViewSet(OrgBulkModelViewSet):
         'su_from_accounts': 'accounts.view_account',
         'clear_secret': 'accounts.change_account',
     }
+    export_as_zip = True
 
     @action(methods=['get'], detail=False, url_path='su-from-accounts')
     def su_from_accounts(self, request, *args, **kwargs):
diff --git a/apps/accounts/api/account/template.py b/apps/accounts/api/account/template.py
index 11675368f..0aecb5143 100644
--- a/apps/accounts/api/account/template.py
+++ b/apps/accounts/api/account/template.py
@@ -1,4 +1,6 @@
 from django_filters import rest_framework as drf_filters
+from rest_framework.decorators import action
+from rest_framework.response import Response
 
 from accounts import serializers
 from accounts.models import AccountTemplate
@@ -38,8 +40,20 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
     filterset_class = AccountTemplateFilterSet
     search_fields = ('username', 'name')
     serializer_classes = {
-        'default': serializers.AccountTemplateSerializer
+        'default': serializers.AccountTemplateSerializer,
     }
+    rbac_perms = {
+        'su_from_account_templates': 'accounts.view_accounttemplate',
+    }
+
+    @action(methods=['get'], detail=False, url_path='su-from-account-templates')
+    def su_from_account_templates(self, request, *args, **kwargs):
+        pk = request.query_params.get('template_id')
+        template = AccountTemplate.objects.filter(pk=pk).first()
+        templates = AccountTemplate.get_su_from_account_templates(template)
+        templates = self.filter_queryset(templates)
+        serializer = self.get_serializer(templates, many=True)
+        return Response(data=serializer.data)
 
 
 class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
diff --git a/apps/accounts/automations/change_secret/host/aix/main.yml b/apps/accounts/automations/change_secret/host/aix/main.yml
index 4bb571f62..b51ddf69e 100644
--- a/apps/accounts/automations/change_secret/host/aix/main.yml
+++ b/apps/accounts/automations/change_secret/host/aix/main.yml
@@ -9,6 +9,7 @@
         name: "{{ account.username }}"
         password: "{{ account.secret | password_hash('des') }}"
         update_password: always
+      ignore_errors: true
       when: account.secret_type == "password"
 
     - name: create user If it already exists, no operation will be performed
diff --git a/apps/accounts/automations/change_secret/host/posix/main.yml b/apps/accounts/automations/change_secret/host/posix/main.yml
index 8dea25c12..80f0aa01c 100644
--- a/apps/accounts/automations/change_secret/host/posix/main.yml
+++ b/apps/accounts/automations/change_secret/host/posix/main.yml
@@ -9,6 +9,7 @@
         name: "{{ account.username }}"
         password: "{{ account.secret | password_hash('sha512') }}"
         update_password: always
+      ignore_errors: true
       when: account.secret_type == "password"
 
     - name: create user If it already exists, no operation will be performed
diff --git a/apps/accounts/automations/change_secret/host/windows/main.yml b/apps/accounts/automations/change_secret/host/windows/main.yml
index 66efb0801..86ea7a81f 100644
--- a/apps/accounts/automations/change_secret/host/windows/main.yml
+++ b/apps/accounts/automations/change_secret/host/windows/main.yml
@@ -21,6 +21,7 @@
         groups: "{{ user_info.groups[0].name }}"
         groups_action: add
         update_password: always
+      ignore_errors: true
       when: account.secret_type == "password"
 
     - name: Refresh connection
diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py
index 05e2b1349..9d1c2f441 100644
--- a/apps/accounts/automations/change_secret/manager.py
+++ b/apps/accounts/automations/change_secret/manager.py
@@ -72,14 +72,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
             return []
 
         asset = privilege_account.asset
-        accounts = asset.accounts.exclude(username=privilege_account.username)
+        accounts = asset.accounts.all()
         accounts = accounts.filter(id__in=self.account_ids)
         if self.secret_type:
             accounts = accounts.filter(secret_type=self.secret_type)
 
         if settings.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED:
             accounts = accounts.filter(privileged=False).exclude(
-                username__in=['root', 'administrator']
+                username__in=['root', 'administrator', privilege_account.username]
             )
         return accounts
 
diff --git a/apps/accounts/automations/gather_accounts/filter.py b/apps/accounts/automations/gather_accounts/filter.py
index af7cdefa4..c6db6dbd4 100644
--- a/apps/accounts/automations/gather_accounts/filter.py
+++ b/apps/accounts/automations/gather_accounts/filter.py
@@ -13,8 +13,8 @@ class GatherAccountsFilter:
     def mysql_filter(info):
         result = {}
         for _, user_dict in info.items():
-            for username, data in user_dict.items():
-                if data.get('account_locked') == 'N':
+            for username, _ in user_dict.items():
+                if len(username.split('.')) == 1:
                     result[username] = {}
         return result
 
diff --git a/apps/accounts/automations/push_account/host/aix/main.yml b/apps/accounts/automations/push_account/host/aix/main.yml
index 9ac68d20e..7c43c5220 100644
--- a/apps/accounts/automations/push_account/host/aix/main.yml
+++ b/apps/accounts/automations/push_account/host/aix/main.yml
@@ -43,6 +43,7 @@
         name: "{{ account.username }}"
         password: "{{ account.secret | password_hash('sha512') }}"
         update_password: always
+      ignore_errors: true
       when: account.secret_type == "password"
 
     - name: remove jumpserver ssh key
diff --git a/apps/accounts/automations/push_account/host/posix/main.yml b/apps/accounts/automations/push_account/host/posix/main.yml
index 9ac68d20e..7c43c5220 100644
--- a/apps/accounts/automations/push_account/host/posix/main.yml
+++ b/apps/accounts/automations/push_account/host/posix/main.yml
@@ -43,6 +43,7 @@
         name: "{{ account.username }}"
         password: "{{ account.secret | password_hash('sha512') }}"
         update_password: always
+      ignore_errors: true
       when: account.secret_type == "password"
 
     - name: remove jumpserver ssh key
diff --git a/apps/accounts/automations/push_account/host/windows/main.yml b/apps/accounts/automations/push_account/host/windows/main.yml
index 8a2a0aef0..17f68b660 100644
--- a/apps/accounts/automations/push_account/host/windows/main.yml
+++ b/apps/accounts/automations/push_account/host/windows/main.yml
@@ -17,6 +17,7 @@
         groups: "{{ params.groups }}"
         groups_action: add
         update_password: always
+      ignore_errors: true
       when: account.secret_type == "password"
 
     - name: Refresh connection
diff --git a/apps/accounts/automations/verify_account/custom/main.yml b/apps/accounts/automations/verify_account/custom/main.yml
index 6ad8cd98b..cf4a937a7 100644
--- a/apps/accounts/automations/verify_account/custom/main.yml
+++ b/apps/accounts/automations/verify_account/custom/main.yml
@@ -10,5 +10,5 @@
         login_port: "{{ jms_asset.port }}"
         login_user: "{{ account.username }}"
         login_password: "{{ account.secret }}"
-        login_secret_type: "{{ jms_account.secret_type }}"
-        login_private_key_path: "{{ jms_account.private_key_path }}"
+        login_secret_type: "{{ account.secret_type }}"
+        login_private_key_path: "{{ account.private_key_path }}"
diff --git a/apps/accounts/filters.py b/apps/accounts/filters.py
index fa29bcbfc..67e243e1c 100644
--- a/apps/accounts/filters.py
+++ b/apps/accounts/filters.py
@@ -5,7 +5,6 @@ from django_filters import rest_framework as drf_filters
 
 from assets.models import Node
 from common.drf.filters import BaseFilterSet
-
 from .models import Account, GatheredAccount
 
 
@@ -46,7 +45,7 @@ class AccountFilterSet(BaseFilterSet):
 
     class Meta:
         model = Account
-        fields = ['id', 'asset_id']
+        fields = ['id', 'asset_id', 'source_id']
 
 
 class GatheredAccountFilterSet(BaseFilterSet):
diff --git a/apps/accounts/migrations/0011_auto_20230506_1443.py b/apps/accounts/migrations/0011_auto_20230506_1443.py
new file mode 100644
index 000000000..3460376bd
--- /dev/null
+++ b/apps/accounts/migrations/0011_auto_20230506_1443.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.17 on 2023-05-06 06:43
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('accounts', '0010_gatheraccountsautomation_is_sync_account'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='accounttemplate',
+            name='su_from',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='accounts.accounttemplate', verbose_name='Su from'),
+        ),
+        migrations.AlterField(
+            model_name='changesecretautomation',
+            name='ssh_key_change_strategy',
+            field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'),
+        ),
+        migrations.AlterField(
+            model_name='pushaccountautomation',
+            name='ssh_key_change_strategy',
+            field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'),
+        ),
+    ]
diff --git a/apps/accounts/models/account.py b/apps/accounts/models/account.py
index 4094018e1..30eb853e3 100644
--- a/apps/accounts/models/account.py
+++ b/apps/accounts/models/account.py
@@ -1,4 +1,6 @@
 from django.db import models
+from django.db.models import Count, Q
+from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from simple_history.models import HistoricalRecords
 
@@ -106,6 +108,11 @@ class Account(AbsConnectivity, BaseAccount):
 
 
 class AccountTemplate(BaseAccount):
+    su_from = models.ForeignKey(
+        'self', related_name='su_to', null=True,
+        on_delete=models.SET_NULL, verbose_name=_("Su from")
+    )
+
     class Meta:
         verbose_name = _('Account template')
         unique_together = (
@@ -116,5 +123,62 @@ class AccountTemplate(BaseAccount):
             ('change_accounttemplatesecret', _('Can change asset account template secret')),
         ]
 
+    @classmethod
+    def get_su_from_account_templates(cls, instance=None):
+        if not instance:
+            return cls.objects.all()
+        return cls.objects.exclude(Q(id=instance.id) | Q(su_from=instance))
+
+    def get_su_from_account(self, asset):
+        su_from = self.su_from
+        if su_from and asset.platform.su_enabled:
+            account = asset.accounts.filter(
+                username=su_from.username,
+                secret_type=su_from.secret_type
+            ).first()
+            return account
+
     def __str__(self):
         return self.username
+
+    @staticmethod
+    def bulk_update_accounts(accounts, data):
+        history_model = Account.history.model
+        account_ids = accounts.values_list('id', flat=True)
+        history_accounts = history_model.objects.filter(id__in=account_ids)
+        account_id_count_map = {
+            str(i['id']): i['count']
+            for i in history_accounts.values('id').order_by('id')
+            .annotate(count=Count(1)).values('id', 'count')
+        }
+
+        for account in accounts:
+            account_id = str(account.id)
+            account.version = account_id_count_map.get(account_id) + 1
+            for k, v in data.items():
+                setattr(account, k, v)
+        Account.objects.bulk_update(accounts, ['version', 'secret'])
+
+    @staticmethod
+    def bulk_create_history_accounts(accounts, user_id):
+        history_model = Account.history.model
+        history_account_objs = []
+        for account in accounts:
+            history_account_objs.append(
+                history_model(
+                    id=account.id,
+                    version=account.version,
+                    secret=account.secret,
+                    secret_type=account.secret_type,
+                    history_user_id=user_id,
+                    history_date=timezone.now()
+                )
+            )
+        history_model.objects.bulk_create(history_account_objs)
+
+    def bulk_sync_account_secret(self, accounts, user_id):
+        """ 批量同步账号密码 """
+        if not accounts:
+            return
+        self.bulk_update_accounts(accounts, {'secret': self.secret})
+        self.bulk_create_history_accounts(accounts, user_id)
diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py
index c76cb1788..7b4abefa2 100644
--- a/apps/accounts/serializers/account/account.py
+++ b/apps/accounts/serializers/account/account.py
@@ -1,4 +1,5 @@
 import uuid
+from copy import deepcopy
 
 from django.db import IntegrityError
 from django.db.models import Q
@@ -21,8 +22,8 @@ logger = get_logger(__name__)
 
 class AccountCreateUpdateSerializerMixin(serializers.Serializer):
     template = serializers.PrimaryKeyRelatedField(
-        queryset=AccountTemplate.objects,
-        required=False, label=_("Template"), write_only=True
+        queryset=AccountTemplate.objects, required=False,
+        label=_("Template"), write_only=True, allow_null=True
     )
     push_now = serializers.BooleanField(
         default=False, label=_("Push now"), write_only=True
@@ -32,9 +33,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
     )
     on_invalid = LabeledChoiceField(
         choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
-        write_only=True, label=_('Exist policy')
+        write_only=True, allow_null=True, label=_('Exist policy'),
     )
     _template = None
+    clean_auth_fields: callable
 
     class Meta:
         fields = ['template', 'push_now', 'params', 'on_invalid']
@@ -91,7 +93,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
 
         self._template = template
         # Set initial data from template
-        ignore_fields = ['id', 'date_created', 'date_updated', 'org_id']
+        ignore_fields = ['id', 'date_created', 'date_updated', 'su_from', 'org_id']
         field_names = [
             field.name for field in template._meta.fields
             if field.name not in ignore_fields
@@ -151,12 +153,14 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
         template = self._template
         if template is None:
             return
+
         validated_data['source'] = Source.TEMPLATE
         validated_data['source_id'] = str(template.id)
 
     def create(self, validated_data):
         push_now = validated_data.pop('push_now', None)
         params = validated_data.pop('params', None)
+        self.clean_auth_fields(validated_data)
         self.generate_source_data(validated_data)
         instance, stat = self.do_create(validated_data)
         self.push_account_if_need(instance, push_now, params, stat)
@@ -238,14 +242,18 @@ class AssetAccountBulkSerializerResultSerializer(serializers.Serializer):
 class AssetAccountBulkSerializer(
     AccountCreateUpdateSerializerMixin, AuthValidateMixin, serializers.ModelSerializer
 ):
+    su_from_username = serializers.CharField(
+        max_length=128, required=False, write_only=True, allow_null=True, label=_("Su from"),
+        allow_blank=True,
+    )
     assets = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, many=True, label=_('Assets'))
 
     class Meta:
         model = Account
         fields = [
-            'name', 'username', 'secret', 'secret_type',
+            'name', 'username', 'secret', 'secret_type', 'passphrase',
             'privileged', 'is_active', 'comment', 'template',
-            'on_invalid', 'push_now', 'assets',
+            'on_invalid', 'push_now', 'assets', 'su_from_username'
         ]
         extra_kwargs = {
             'name': {'required': False},
@@ -293,8 +301,21 @@ class AssetAccountBulkSerializer(
             raise serializers.ValidationError(_('Account already exists'))
         return instance, True, 'created'
 
+    def generate_su_from_data(self, validated_data):
+        template = self._template
+        asset = validated_data['asset']
+        su_from = validated_data.get('su_from')
+        su_from_username = validated_data.pop('su_from_username', None)
+        if template:
+            su_from = template.get_su_from_account(asset)
+        elif su_from_username:
+            su_from = asset.accounts.filter(username=su_from_username).first()
+        validated_data['su_from'] = su_from
+
     def perform_create(self, vd, handler):
         lookup = self.get_filter_lookup(vd)
+        vd = deepcopy(vd)
+        self.generate_su_from_data(vd)
         try:
             instance, changed, state = handler(vd, lookup)
         except IntegrityError:
@@ -335,6 +356,7 @@ class AssetAccountBulkSerializer(
             vd = vd.copy()
             vd['asset'] = asset
             try:
+                self.clean_auth_fields(vd)
                 instance, changed, state = self.perform_create(vd, create_handler)
                 _results[asset] = {
                     'changed': changed, 'instance': instance.id, 'state': state
diff --git a/apps/accounts/serializers/account/backup.py b/apps/accounts/serializers/account/backup.py
index af07ca04d..712bdd095 100644
--- a/apps/accounts/serializers/account/backup.py
+++ b/apps/accounts/serializers/account/backup.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext_lazy as _
 from rest_framework import serializers
 
 from accounts.models import AccountBackupAutomation, AccountBackupExecution
diff --git a/apps/accounts/serializers/account/base.py b/apps/accounts/serializers/account/base.py
index 4e2b1a1df..b79dd51be 100644
--- a/apps/accounts/serializers/account/base.py
+++ b/apps/accounts/serializers/account/base.py
@@ -78,4 +78,5 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
         ]
         extra_kwargs = {
             'spec_info': {'label': _('Spec info')},
+            'username': {'help_text': _("Tip: If no username is required for authentication, fill in `null`")}
         }
diff --git a/apps/accounts/serializers/account/template.py b/apps/accounts/serializers/account/template.py
index 1de4d7c1d..d2d992ea6 100644
--- a/apps/accounts/serializers/account/template.py
+++ b/apps/accounts/serializers/account/template.py
@@ -1,86 +1,44 @@
-from django.db.transaction import atomic
-from django.db.utils import IntegrityError
+from django.utils.translation import ugettext_lazy as _
+from rest_framework import serializers
 
 from accounts.models import AccountTemplate, Account
-from assets.models import Asset
 from common.serializers import SecretReadableMixin
+from common.serializers.fields import ObjectRelatedField
 from .base import BaseAccountSerializer
 
 
 class AccountTemplateSerializer(BaseAccountSerializer):
+    is_sync_account = serializers.BooleanField(default=False, write_only=True)
+    _is_sync_account = False
+
+    su_from = ObjectRelatedField(
+        required=False, queryset=AccountTemplate.objects, allow_null=True,
+        allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username')
+    )
+
     class Meta(BaseAccountSerializer.Meta):
         model = AccountTemplate
+        fields = BaseAccountSerializer.Meta.fields + ['is_sync_account', 'su_from']
 
-    @staticmethod
-    def account_save(data, account):
-        for field, value in data.items():
-            setattr(account, field, value)
-        try:
-            account.save(update_fields=list(data.keys()))
-        except IntegrityError:
-            pass
-
-    # TODO 数据库访问的太多了 后期优化
-    @atomic()
-    def bulk_update_accounts(self, instance, diff):
-        accounts = Account.objects.filter(source_id=instance.id)
-        if not accounts:
+    def sync_accounts_secret(self, instance, diff):
+        if not self._is_sync_account or 'secret' not in diff:
             return
 
-        diff.pop('secret', None)
-        name = diff.pop('name', None)
-        username = diff.pop('username', None)
-        secret_type = diff.pop('secret_type', None)
-        update_accounts = []
-        for account in accounts:
-            for field, value in diff.items():
-                setattr(account, field, value)
-                update_accounts.append(account)
+        accounts = Account.objects.filter(source_id=instance.id)
+        instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
 
-        if update_accounts:
-            Account.objects.bulk_update(update_accounts, diff.keys())
-
-        if name:
-            for account in accounts:
-                data = {'name': name}
-                self.account_save(data, account)
-
-        if secret_type and username:
-            asset_ids_supports = self.get_asset_ids_supports(accounts, secret_type)
-            for account in accounts:
-                asset_id = account.asset_id
-                if asset_id not in asset_ids_supports:
-                    data = {'username': username}
-                    self.account_save(data, account)
-                    continue
-                data = {'username': username, 'secret_type': secret_type, 'secret': instance.secret}
-                self.account_save(data, account)
-        elif secret_type:
-            asset_ids_supports = self.get_asset_ids_supports(accounts, secret_type)
-            for account in accounts:
-                asset_id = account.asset_id
-                if asset_id not in asset_ids_supports:
-                    continue
-                data = {'secret_type': secret_type, 'secret': instance.secret}
-                self.account_save(data, account)
-        elif username:
-            for account in accounts:
-                data = {'username': username}
-                self.account_save(data, account)
-
-    @staticmethod
-    def get_asset_ids_supports(accounts, secret_type):
-        asset_ids = accounts.values_list('asset_id', flat=True)
-        secret_type_supports = Asset.get_secret_type_assets(asset_ids, secret_type)
-        return [asset.id for asset in secret_type_supports]
+    def validate(self, attrs):
+        self._is_sync_account = attrs.pop('is_sync_account', None)
+        attrs = super().validate(attrs)
+        return attrs
 
     def update(self, instance, validated_data):
-        # diff = {
-        #     k: v for k, v in validated_data.items()
-        #     if getattr(instance, k) != v
-        # }
+        diff = {
+            k: v for k, v in validated_data.items()
+            if getattr(instance, k, None) != v
+        }
         instance = super().update(instance, validated_data)
-        # self.bulk_update_accounts(instance, diff)
+        self.sync_accounts_secret(instance, diff)
         return instance
 
 
diff --git a/apps/accounts/serializers/automations/base.py b/apps/accounts/serializers/automations/base.py
index cdb08bf36..1468ecf58 100644
--- a/apps/accounts/serializers/automations/base.py
+++ b/apps/accounts/serializers/automations/base.py
@@ -1,4 +1,4 @@
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext_lazy as _
 from rest_framework import serializers
 
 from accounts.models import AutomationExecution
diff --git a/apps/acls/serializers/base.py b/apps/acls/serializers/base.py
index 069a92a90..c1a2f0a1f 100644
--- a/apps/acls/serializers/base.py
+++ b/apps/acls/serializers/base.py
@@ -2,6 +2,7 @@ from django.utils.translation import ugettext_lazy as _
 from rest_framework import serializers
 
 from acls.models.base import ActionChoices
+from jumpserver.utils import has_valid_xpack_license
 from common.serializers.fields import JSONManyToManyField, ObjectRelatedField, LabeledChoiceField
 from orgs.models import Organization
 from users.models import User
@@ -51,7 +52,26 @@ class ACLAccountsSerializer(serializers.Serializer):
     )
 
 
-class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer):
+class ActionAclSerializer(serializers.Serializer):
+    action = LabeledChoiceField(
+        choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action")
+    )
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.set_action_choices()
+
+    def set_action_choices(self):
+        action = self.fields.get("action")
+        if not action:
+            return
+        choices = action.choices
+        if not has_valid_xpack_license():
+            choices.pop(ActionChoices.review, None)
+        action._choices = choices
+
+
+class BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Serializer):
     users = JSONManyToManyField(label=_('User'))
     assets = JSONManyToManyField(label=_('Asset'))
     accounts = serializers.ListField(label=_('Account'))
@@ -61,9 +81,6 @@ class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer):
     reviewers_amount = serializers.IntegerField(
         read_only=True, source="reviewers.count", label=_('Reviewers amount')
     )
-    action = LabeledChoiceField(
-        choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action")
-    )
 
     class Meta:
         fields_mini = ["id", "name"]
diff --git a/apps/acls/serializers/login_acl.py b/apps/acls/serializers/login_acl.py
index 0a180d9e5..07e499ed6 100644
--- a/apps/acls/serializers/login_acl.py
+++ b/apps/acls/serializers/login_acl.py
@@ -2,12 +2,11 @@ from django.utils.translation import ugettext as _
 from rest_framework import serializers
 
 from common.serializers import BulkModelSerializer, MethodSerializer
-from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
-from jumpserver.utils import has_valid_xpack_license
+from common.serializers.fields import ObjectRelatedField
 from users.models import User
+from .base import ActionAclSerializer
 from .rules import RuleSerializer
 from ..models import LoginACL
-from ..models.base import ActionChoices
 
 __all__ = [
     "LoginACLSerializer",
@@ -18,12 +17,11 @@ common_help_text = _(
 )
 
 
-class LoginACLSerializer(BulkModelSerializer):
+class LoginACLSerializer(ActionAclSerializer, BulkModelSerializer):
     user = ObjectRelatedField(queryset=User.objects, label=_("User"))
     reviewers = ObjectRelatedField(
         queryset=User.objects, label=_("Reviewers"), many=True, required=False
     )
-    action = LabeledChoiceField(choices=ActionChoices.choices, label=_('Action'))
     reviewers_amount = serializers.IntegerField(
         read_only=True, source="reviewers.count", label=_("Reviewers amount")
     )
@@ -45,18 +43,5 @@ class LoginACLSerializer(BulkModelSerializer):
             "is_active": {"default": True},
         }
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.set_action_choices()
-
-    def set_action_choices(self):
-        action = self.fields.get("action")
-        if not action:
-            return
-        choices = action.choices
-        if not has_valid_xpack_license():
-            choices.pop(LoginACL.ActionChoices.review, None)
-        action._choices = choices
-
     def get_rules_serializer(self):
         return RuleSerializer()
diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py
index 71d912188..ce5d8f9ac 100644
--- a/apps/assets/api/asset/asset.py
+++ b/apps/assets/api/asset/asset.py
@@ -25,7 +25,7 @@ from ...notifications import BulkUpdatePlatformSkipAssetUserMsg
 logger = get_logger(__file__)
 __all__ = [
     "AssetViewSet", "AssetTaskCreateApi",
-    "AssetsTaskCreateApi", 'AssetFilterSet'
+    "AssetsTaskCreateApi", 'AssetFilterSet',
 ]
 
 
@@ -95,7 +95,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
     """
     model = Asset
     filterset_class = AssetFilterSet
-    search_fields = ("name", "address")
+    search_fields = ("name", "address", "comment")
     ordering_fields = ('name', 'connectivity', 'platform', 'date_updated')
     serializer_classes = (
         ("default", serializers.AssetSerializer),
diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py
index 13ae0f283..39d51197b 100644
--- a/apps/assets/api/mixin.py
+++ b/apps/assets/api/mixin.py
@@ -69,7 +69,7 @@ class SerializeToTreeNodeMixin:
             return 'file'
 
     @timeit
-    def serialize_assets(self, assets, node_key=None):
+    def serialize_assets(self, assets, node_key=None, pid=None):
         sftp_enabled_platform = PlatformProtocol.objects \
             .filter(name='ssh', setting__sftp_enabled=True) \
             .values_list('platform', flat=True) \
@@ -83,8 +83,10 @@ class SerializeToTreeNodeMixin:
             {
                 'id': str(asset.id),
                 'name': asset.name,
-                'title': f'{asset.address}\n{asset.comment}',
-                'pId': get_pid(asset),
+                'title':
+                    f'{asset.address}\n{asset.comment}'
+                    if asset.comment else asset.address,
+                'pId': pid or get_pid(asset),
                 'isParent': False,
                 'open': False,
                 'iconSkin': self.get_icon(asset),
diff --git a/apps/assets/api/tree.py b/apps/assets/api/tree.py
index 9c9ffa8c9..f27afd019 100644
--- a/apps/assets/api/tree.py
+++ b/apps/assets/api/tree.py
@@ -163,8 +163,10 @@ class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
         # 资源数量统计可选项 (asset, account)
         count_resource = self.request.query_params.get('count_resource', 'asset')
 
-        if include_asset and self.request.query_params.get('key'):
+        if not self.request.query_params.get('key'):
+            nodes = AllTypes.to_tree_nodes(include_asset, count_resource=count_resource)
+        elif include_asset:
             nodes = self.get_assets()
         else:
-            nodes = AllTypes.to_tree_nodes(include_asset, count_resource=count_resource)
+            nodes = []
         return Response(data=nodes)
diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py
index ae9740347..aa31c886f 100644
--- a/apps/assets/automations/base/manager.py
+++ b/apps/assets/automations/base/manager.py
@@ -166,6 +166,7 @@ class BasePlaybookManager:
             account_prefer=self.ansible_account_prefer,
             account_policy=self.ansible_account_policy,
             host_callback=self.host_callback,
+            task_type=self.__class__.method_type(),
         )
         inventory.write_to_file(inventory_path)
 
@@ -223,7 +224,8 @@ class BasePlaybookManager:
         pass
 
     def on_host_error(self, host, error, result):
-        print('host error: {} -> {}'.format(host, error))
+        if settings.DEBUG_DEV:
+            print('host error: {} -> {}'.format(host, error))
 
     def on_runner_success(self, runner, cb):
         summary = cb.summary
diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py
index 8194c5c14..69fa36bb3 100644
--- a/apps/assets/const/types.py
+++ b/apps/assets/const/types.py
@@ -193,15 +193,38 @@ class AllTypes(ChoicesMixin):
         }
         return node
 
-    @classmethod
-    def to_tree_nodes(cls, include_asset, count_resource='asset'):
-        from accounts.models import Account
-        from ..models import Asset, Platform
-        if count_resource == 'account':
-            resource_platforms = Account.objects.all().values_list('asset__platform_id', flat=True)
-        else:
-            resource_platforms = Asset.objects.all().values_list('platform_id', flat=True)
 
+    @classmethod
+    def asset_to_node(cls, asset, pid):
+        node = {
+            'id': '{}'.format(asset.id),
+            'name': asset.name,
+            'title': f'{asset.address}\n{asset.comment}',
+            'pId': pid,
+            'isParent': False,
+            'open': False,
+            'iconSkin': asset.type,
+            'chkDisabled': not asset.is_active,
+            'meta': {
+                'type': 'platform',
+                'data': {
+                    'platform_type': asset.platform.type,
+                    'org_name': asset.org_name,
+                    # 'sftp': asset.platform_id in sftp_enabled_platform,
+                    'name': asset.name,
+                    'address': asset.address
+                },
+            }
+        }
+        return node
+
+    @classmethod
+    def get_root_nodes(cls):
+        return dict(id='ROOT', name=_('All types'), title=_('All types'), open=True, isParent=True)
+
+    @classmethod
+    def get_tree_nodes(cls, resource_platforms, include_asset=False):
+        from ..models import Platform
         platform_count = defaultdict(int)
         for platform_id in resource_platforms:
             platform_count[platform_id] += 1
@@ -215,8 +238,7 @@ class AllTypes(ChoicesMixin):
             category_type_mapper[p.category] += platform_count[p.id]
             tp_platforms[p.category + '_' + p.type].append(p)
 
-        root = dict(id='ROOT', name=_('All types'), title=_('All types'), open=True, isParent=True)
-        nodes = [root]
+        nodes = [cls.get_root_nodes()]
         for category, type_cls in cls.category_types():
             # Category 格式化
             meta = {'type': 'category', 'category': category.value}
@@ -244,6 +266,16 @@ class AllTypes(ChoicesMixin):
                     nodes.append(platform_node)
         return nodes
 
+    @classmethod
+    def to_tree_nodes(cls, include_asset, count_resource='asset'):
+        from accounts.models import Account
+        from ..models import Asset
+        if count_resource == 'account':
+            resource_platforms = Account.objects.all().values_list('asset__platform_id', flat=True)
+        else:
+            resource_platforms = Asset.objects.all().values_list('platform_id', flat=True)
+        return cls.get_tree_nodes(resource_platforms, include_asset)
+
     @classmethod
     def get_type_default_platform(cls, category, tp):
         constraints = cls.get_constraints(category, tp)
diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py
index 5fd333262..64a85ecb6 100644
--- a/apps/assets/migrations/0098_auto_20220430_2126.py
+++ b/apps/assets/migrations/0098_auto_20220430_2126.py
@@ -20,12 +20,13 @@ def get_prop_name_id(apps, app, category):
 
 
 def migrate_database_to_asset(apps, *args):
+    node_model = apps.get_model('assets', 'Node')
     app_model = apps.get_model('applications', 'Application')
     db_model = apps.get_model('assets', 'Database')
     platform_model = apps.get_model('assets', 'Platform')
 
     applications = app_model.objects.filter(category='db')
-    platforms = platform_model.objects.all().filter(internal=True)
+    platforms = platform_model.objects.all().filter(internal=True).exclude(name='Redis6+')
     platforms_map = {p.type: p for p in platforms}
     print()
 
@@ -84,11 +85,18 @@ def create_app_nodes(apps, org_id):
     node_keys = node_model.objects.filter(org_id=org_id) \
         .filter(key__regex=child_pattern) \
         .values_list('key', flat=True)
-    if not node_keys:
-        return
-    node_key_split = [key.split(':') for key in node_keys]
-    next_value = max([int(k[1]) for k in node_key_split]) + 1
-    parent_key = node_key_split[0][0]
+    if node_keys:
+        node_key_split = [key.split(':') for key in node_keys]
+        next_value = max([int(k[1]) for k in node_key_split]) + 1
+        parent_key = node_key_split[0][0]
+    else:
+        root_node = node_model.objects.filter(org_id=org_id)\
+            .filter(parent_key='', key__regex=r'^[0-9]+$').exclude(key__startswith='-').first()
+        if not root_node:
+            return
+        parent_key = root_node.key
+        next_value = 0
+
     next_key = '{}:{}'.format(parent_key, next_value)
     name = 'Apps'
     parent = node_model.objects.get(key=parent_key)
diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py
index 87eed5fb0..4e57be633 100644
--- a/apps/assets/migrations/0100_auto_20220711_1413.py
+++ b/apps/assets/migrations/0100_auto_20220711_1413.py
@@ -161,11 +161,12 @@ def migrate_db_accounts(apps, schema_editor):
                     name = f'{username}(token)'
                 else:
                     secret_type = attr
-                    name = username
+                    name = username or f'{username}(password)'
                 auth_infos.append((name, secret_type, secret))
 
             if not auth_infos:
-                auth_infos.append((username, 'password', ''))
+                name = username or f'{username}(password)'
+                auth_infos.append((name, 'password', ''))
 
             for name, secret_type, secret in auth_infos:
                 values['name'] = name
diff --git a/apps/assets/migrations/0117_alter_baseautomation_params.py b/apps/assets/migrations/0117_alter_baseautomation_params.py
new file mode 100644
index 000000000..1fc93bdd3
--- /dev/null
+++ b/apps/assets/migrations/0117_alter_baseautomation_params.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.17 on 2023-05-06 06:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('assets', '0116_auto_20230418_1726'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='baseautomation',
+            name='params',
+            field=models.JSONField(default=dict, verbose_name='Parameters'),
+        ),
+    ]
diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py
index 030e54de3..f50177642 100644
--- a/apps/assets/models/platform.py
+++ b/apps/assets/models/platform.py
@@ -26,14 +26,6 @@ class PlatformProtocol(models.Model):
     def secret_types(self):
         return Protocol.settings().get(self.name, {}).get('secret_types', ['password'])
 
-    def set_public(self):
-        private_protocol_set = ('winrm',)
-        self.public = self.name not in private_protocol_set
-
-    def save(self, **kwargs):
-        self.set_public()
-        return super().save(**kwargs)
-
 
 class PlatformAutomation(models.Model):
     ansible_enabled = models.BooleanField(default=False, verbose_name=_("Enabled"))
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index 0fd14eec9..88a6cfef2 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -23,7 +23,7 @@ __all__ = [
     'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
     'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer',
     'AssetDetailSerializer', 'DetailMixin', 'AssetAccountSerializer',
-    'AccountSecretSerializer', 'AssetProtocolsPermsSerializer'
+    'AccountSecretSerializer', 'AssetProtocolsPermsSerializer', 'AssetLabelSerializer'
 ]
 
 
diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py
index da6c9574a..386cb87e9 100644
--- a/apps/assets/serializers/asset/database.py
+++ b/apps/assets/serializers/asset/database.py
@@ -1,5 +1,6 @@
 from django.utils.translation import ugettext_lazy as _
 from rest_framework.serializers import ValidationError
+from rest_framework import serializers
 
 from assets.models import Database
 from assets.serializers.gateway import GatewayWithAccountSecretSerializer
@@ -9,6 +10,8 @@ __all__ = ['DatabaseSerializer', 'DatabaseWithGatewaySerializer']
 
 
 class DatabaseSerializer(AssetSerializer):
+    db_name = serializers.CharField(max_length=1024, label=_('Default database'), required=True)
+
     class Meta(AssetSerializer.Meta):
         model = Database
         extra_fields = [
diff --git a/apps/assets/serializers/automations/base.py b/apps/assets/serializers/automations/base.py
index 804bcabc9..527f71628 100644
--- a/apps/assets/serializers/automations/base.py
+++ b/apps/assets/serializers/automations/base.py
@@ -1,12 +1,12 @@
 from django.utils.translation import ugettext as _
 from rest_framework import serializers
 
-from ops.mixin import PeriodTaskSerializerMixin
 from assets.models import Asset, Node, BaseAutomation, AutomationExecution
-from orgs.mixins.serializers import BulkOrgResourceModelSerializer
-from common.utils import get_logger
 from common.const.choices import Trigger
 from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
+from common.utils import get_logger
+from ops.mixin import PeriodTaskSerializerMixin
+from orgs.mixins.serializers import BulkOrgResourceModelSerializer
 
 logger = get_logger(__file__)
 
@@ -48,9 +48,12 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
 
     @staticmethod
     def get_snapshot(obj):
+        from assets.const import AutomationTypes as AssetTypes
+        from accounts.const import AutomationTypes as AccountTypes
+        tp_dict = dict(AssetTypes.choices) | dict(AccountTypes.choices)
         tp = obj.snapshot['type']
         snapshot = {
-            'type': tp,
+            'type': {'value': tp, 'label': tp_dict.get(tp, tp)},
             'name': obj.snapshot['name'],
             'comment': obj.snapshot['comment'],
             'accounts': obj.snapshot['accounts'],
diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py
index 3313168cc..80421d41b 100644
--- a/apps/assets/serializers/platform.py
+++ b/apps/assets/serializers/platform.py
@@ -82,7 +82,7 @@ class PlatformProtocolSerializer(serializers.ModelSerializer):
         model = PlatformProtocol
         fields = [
             "id", "name", "port", "primary",
-            "required", "default",
+            "required", "default", "public",
             "secret_types", "setting",
         ]
 
@@ -122,6 +122,7 @@ class PlatformSerializer(WritableNestedModelSerializer):
         fields_small = fields_mini + [
             "category", "type", "charset",
         ]
+        fields_unexport = ['automation']
         read_only_fields = [
             'internal', 'date_created', 'date_updated',
             'created_by', 'updated_by'
@@ -156,20 +157,6 @@ class PlatformSerializer(WritableNestedModelSerializer):
         constraints = AllTypes.get_constraints(category, tp)
         return constraints
 
-    def validate(self, attrs):
-        domain_enabled = attrs.get('domain_enabled', False) and self.constraints.get('domain_enabled', False)
-        su_enabled = attrs.get('su_enabled', False) and self.constraints.get('su_enabled', False)
-        automation = attrs.get('automation', {})
-        automation['ansible_enabled'] = automation.get('ansible_enabled', False) \
-                                        and self.constraints['automation'].get('ansible_enabled', False)
-        attrs.update({
-            'domain_enabled': domain_enabled,
-            'su_enabled': su_enabled,
-            'automation': automation,
-        })
-        self.initial_data['automation'] = automation
-        return attrs
-
     @classmethod
     def setup_eager_loading(cls, queryset):
         queryset = queryset.prefetch_related(
@@ -187,6 +174,18 @@ class PlatformSerializer(WritableNestedModelSerializer):
         self.initial_data['protocols'] = protocols
         return protocols
 
+    def validate_su_enabled(self, su_enabled):
+        return su_enabled and self.constraints.get('su_enabled', False)
+
+    def validate_domain_enabled(self, domain_enabled):
+        return domain_enabled and self.constraints.get('domain_enabled', False)
+
+    def validate_automation(self, automation):
+        automation = automation or {}
+        automation = automation.get('ansible_enabled', False) \
+                     and self.constraints['automation'].get('ansible_enabled', False)
+        return automation
+
 
 class PlatformOpsMethodSerializer(serializers.Serializer):
     id = serializers.CharField(read_only=True)
diff --git a/apps/audits/handler.py b/apps/audits/handler.py
index 6491c69da..5c069ac09 100644
--- a/apps/audits/handler.py
+++ b/apps/audits/handler.py
@@ -45,10 +45,8 @@ class OperatorLogHandler(metaclass=Singleton):
             pre_value, value = self._consistent_type_to_str(pre_value, value)
             if sorted(str(value)) == sorted(str(pre_value)):
                 continue
-            if pre_value:
-                before[key] = pre_value
-            if value:
-                after[key] = value
+            before[key] = pre_value
+            after[key] = value
         return before, after
 
     def cache_instance_before_data(self, instance_dict):
diff --git a/apps/audits/utils.py b/apps/audits/utils.py
index 27cdd6e28..3865e9326 100644
--- a/apps/audits/utils.py
+++ b/apps/audits/utils.py
@@ -70,8 +70,10 @@ def _get_instance_field_value(
 
             if getattr(f, 'primary_key', False):
                 f.verbose_name = 'id'
-            elif isinstance(value, (list, dict)):
+            elif isinstance(value, list):
                 value = copy.deepcopy(value)
+            elif isinstance(value, dict):
+                value = dict(copy.deepcopy(value))
             elif isinstance(value, datetime):
                 value = as_current_tz(value).strftime('%Y-%m-%d %H:%M:%S')
             elif isinstance(f, models.OneToOneField) and isinstance(value, models.Model):
diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py
index 5b5dcd700..0bc69b025 100644
--- a/apps/authentication/api/connection_token.py
+++ b/apps/authentication/api/connection_token.py
@@ -3,6 +3,7 @@ import json
 import os
 import urllib.parse
 
+from django.conf import settings
 from django.http import HttpResponse
 from django.shortcuts import get_object_or_404
 from django.utils import timezone
@@ -12,14 +13,12 @@ from rest_framework.decorators import action
 from rest_framework.exceptions import PermissionDenied
 from rest_framework.request import Request
 from rest_framework.response import Response
-from rest_framework.serializers import ValidationError
 
-from assets.const import CloudTypes
 from common.api import JMSModelViewSet
 from common.exceptions import JMSException
 from common.utils import random_string, get_logger
 from common.utils.django import get_request_os
-from common.utils.http import is_true
+from common.utils.http import is_true, is_false
 from orgs.mixins.api import RootOrgViewMixin
 from perms.models import ActionChoices
 from terminal.connect_methods import NativeClient, ConnectMethodUtil
@@ -27,7 +26,8 @@ from terminal.models import EndpointRule
 from ..models import ConnectionToken, date_expired_default
 from ..serializers import (
     ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
-    SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer
+    SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer,
+    ConnectionTokenUpdateSerializer
 )
 
 __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet']
@@ -88,7 +88,8 @@ class RDPFileClientProtocolURLMixin:
         if width and height:
             rdp_options['desktopwidth:i'] = width
             rdp_options['desktopheight:i'] = height
-            rdp_options['winposstr:s:'] = f'0,1,0,0,{width},{height}'
+            rdp_options['winposstr:s'] = f'0,1,0,0,{width},{height}'
+            rdp_options['dynamic resolution:i'] = '0'
 
         # 设置其他选项
         rdp_options['session bpp:i'] = os.getenv('JUMPSERVER_COLOR_DEPTH', '32')
@@ -99,6 +100,10 @@ class RDPFileClientProtocolURLMixin:
             remote_app_options = token.get_remote_app_option()
             rdp_options.update(remote_app_options)
 
+        rdp = token.asset.platform.protocols.filter(name='rdp').first()
+        if rdp and rdp.setting.get('console'):
+            rdp_options['administrative session:i'] = '1'
+
         # 文件名
         name = token.asset.name
         prefix_name = f'{token.user.username}-{name}'
@@ -226,10 +231,14 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
     search_fields = filterset_fields
     serializer_classes = {
         'default': ConnectionTokenSerializer,
+        'update': ConnectionTokenUpdateSerializer,
+        'partial_update': ConnectionTokenUpdateSerializer,
     }
+    http_method_names = ['get', 'post', 'patch', 'head', 'options', 'trace']
     rbac_perms = {
         'list': 'authentication.view_connectiontoken',
         'retrieve': 'authentication.view_connectiontoken',
+        'update': 'authentication.change_connectiontoken',
         'create': 'authentication.add_connectiontoken',
         'exchange': 'authentication.add_connectiontoken',
         'expire': 'authentication.change_connectiontoken',
@@ -366,19 +375,26 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
 
         token_id = request.data.get('id') or ''
         token = get_object_or_404(ConnectionToken, pk=token_id)
-        if token.is_expired:
-            raise ValidationError({'id': 'Token is expired'})
-
         token.is_valid()
         serializer = self.get_serializer(instance=token)
-        expire_now = request.data.get('expire_now', True)
 
-        # TODO 暂时特殊处理 k8s 不过期
-        if token.asset.type == CloudTypes.K8S:
-            expire_now = False
+        expire_now = request.data.get('expire_now', None)
+        asset_type = token.asset.type
+        # 设置默认值
+        if expire_now is None:
+            # TODO 暂时特殊处理 k8s 不过期
+            if asset_type in ['k8s', 'kubernetes']:
+                expire_now = False
+            else:
+                expire_now = not settings.CONNECTION_TOKEN_REUSABLE
 
-        if expire_now:
+        if is_false(expire_now):
+            logger.debug('Api specified, now expire now')
+        elif token.is_reusable and settings.CONNECTION_TOKEN_REUSABLE:
+            logger.debug('Token is reusable, not expire now')
+        else:
             token.expire()
+
         return Response(serializer.data, status=status.HTTP_200_OK)
 
     @action(methods=['POST'], detail=False, url_path='applet-option')
diff --git a/apps/authentication/backends/oauth2/backends.py b/apps/authentication/backends/oauth2/backends.py
index 2214ba628..9f3b04ac3 100644
--- a/apps/authentication/backends/oauth2/backends.py
+++ b/apps/authentication/backends/oauth2/backends.py
@@ -104,7 +104,10 @@ class OAuth2Backend(JMSModelBackend):
         headers = {
             'Accept': 'application/json'
         }
-        access_token_response = requests_func(access_token_url, headers=headers)
+        if token_method == 'post':
+            access_token_response = requests_func(access_token_url, headers=headers, json=query_dict)
+        else:
+            access_token_response = requests_func(access_token_url, headers=headers)
         try:
             access_token_response.raise_for_status()
             access_token_response_data = access_token_response.json()
diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py
index d03798f94..43f7bdc14 100644
--- a/apps/authentication/middleware.py
+++ b/apps/authentication/middleware.py
@@ -1,17 +1,16 @@
 import base64
-import time
 
+from django.conf import settings
+from django.contrib.auth import logout as auth_logout
+from django.http import HttpResponse
 from django.shortcuts import redirect, reverse, render
 from django.utils.deprecation import MiddlewareMixin
-from django.http import HttpResponse
-from django.conf import settings
 from django.utils.translation import ugettext as _
-from django.contrib.auth import logout as auth_logout
 
 from apps.authentication import mixins
+from authentication.signals import post_auth_failed
 from common.utils import gen_key_pair
 from common.utils import get_request_ip
-from .signals import post_auth_failed
 
 
 class MFAMiddleware:
@@ -76,12 +75,18 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
         ip = get_request_ip(request)
         try:
             self.request = request
+            self._check_third_party_login_acl()
             self._check_login_acl(request.user, ip)
         except Exception as e:
-            post_auth_failed.send(
-                sender=self.__class__, username=request.user.username,
-                request=self.request, reason=e.msg
-            )
+            if getattr(request, 'user_need_delete', False):
+                request.user.delete()
+            else:
+                error_message = getattr(e, 'msg', None)
+                error_message = error_message or str(e)
+                post_auth_failed.send(
+                    sender=self.__class__, username=request.user.username,
+                    request=self.request, reason=error_message
+                )
             auth_logout(request)
             context = {
                 'title': _('Authentication failed'),
diff --git a/apps/authentication/migrations/0019_connectiontoken_is_reusable.py b/apps/authentication/migrations/0019_connectiontoken_is_reusable.py
new file mode 100644
index 000000000..92ccc892a
--- /dev/null
+++ b/apps/authentication/migrations/0019_connectiontoken_is_reusable.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.17 on 2023-05-08 07:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('authentication', '0018_alter_connectiontoken_input_secret'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='connectiontoken',
+            name='is_reusable',
+            field=models.BooleanField(default=False, verbose_name='Reusable'),
+        ),
+    ]
diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py
index 14e5fd35f..9fce76e57 100644
--- a/apps/authentication/mixins.py
+++ b/apps/authentication/mixins.py
@@ -54,6 +54,7 @@ def authenticate(request=None, **credentials):
     """
     username = credentials.get('username')
 
+    temp_user = None
     for backend, backend_path in _get_backends(return_tuples=True):
         # 检查用户名是否允许认证 (预先检查,不浪费认证时间)
         logger.info('Try using auth backend: {}'.format(str(backend)))
@@ -77,11 +78,19 @@ def authenticate(request=None, **credentials):
 
         # 检查用户是否允许认证
         if not backend.user_allow_authenticate(user):
+            temp_user = user
+            temp_user.backend = backend_path
             continue
 
         # Annotate the user object with the path of the backend.
         user.backend = backend_path
         return user
+    else:
+        if temp_user is not None:
+            source_display = temp_user.source_display
+            request.error_message = _('''The administrator has enabled 'Only allow login from user source'. 
+            The current user source is {}. Please contact the administrator.''').format(source_display)
+            return temp_user
 
     # The credentials supplied are invalid to all backends, fire signal
     user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)
@@ -212,7 +221,8 @@ class MFAMixin:
         self._do_check_user_mfa(code, mfa_type, user=user)
 
     def check_user_mfa_if_need(self, user):
-        if self.request.session.get('auth_mfa'):
+        if self.request.session.get('auth_mfa') and \
+                self.request.session.get('auth_mfa_username') == user.username:
             return
         if not user.mfa_enabled:
             return
@@ -220,15 +230,16 @@ class MFAMixin:
         active_mfa_names = user.active_mfa_backends_mapper.keys()
         raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names))
 
-    def mark_mfa_ok(self, mfa_type):
+    def mark_mfa_ok(self, mfa_type, user):
         self.request.session['auth_mfa'] = 1
+        self.request.session['auth_mfa_username'] = user.username
         self.request.session['auth_mfa_time'] = time.time()
         self.request.session['auth_mfa_required'] = 0
         self.request.session['auth_mfa_type'] = mfa_type
-        MFABlockUtils(self.request.user.username, self.get_request_ip()).clean_failed_count()
+        MFABlockUtils(user.username, self.get_request_ip()).clean_failed_count()
 
     def clean_mfa_mark(self):
-        keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type']
+        keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type', 'auth_mfa_username']
         for k in keys:
             self.request.session.pop(k, '')
 
@@ -263,7 +274,7 @@ class MFAMixin:
             ok, msg = mfa_backend.check_code(code)
 
         if ok:
-            self.mark_mfa_ok(mfa_type)
+            self.mark_mfa_ok(mfa_type, user)
             return
 
         raise errors.MFAFailedError(
@@ -345,6 +356,13 @@ class AuthACLMixin:
             self.request.session['auth_acl_id'] = str(acl.id)
             return
 
+    def _check_third_party_login_acl(self):
+        request = self.request
+        error_message = getattr(request, 'error_message', None)
+        if not error_message:
+            return
+        raise ValueError(error_message)
+
     def check_user_login_confirm_if_need(self, user):
         if not self.request.session.get("auth_confirm_required"):
             return
diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py
index 63049170b..3604d49e5 100644
--- a/apps/authentication/models/connection_token.py
+++ b/apps/authentication/models/connection_token.py
@@ -40,6 +40,7 @@ class ConnectionToken(JMSOrgBaseModel):
     connect_method = models.CharField(max_length=32, verbose_name=_("Connect method"))
     user_display = models.CharField(max_length=128, default='', verbose_name=_("User display"))
     asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display"))
+    is_reusable = models.BooleanField(default=False, verbose_name=_("Reusable"))
     date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_("Date expired"))
     from_ticket = models.OneToOneField(
         'tickets.ApplyLoginAssetTicket', related_name='connection_token',
@@ -74,7 +75,7 @@ class ConnectionToken(JMSOrgBaseModel):
 
     def expire(self):
         self.date_expired = timezone.now()
-        self.save()
+        self.save(update_fields=['date_expired'])
 
     def renewal(self):
         """ 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """
@@ -108,9 +109,8 @@ class ConnectionToken(JMSOrgBaseModel):
             error = _('No user or invalid user')
             raise PermissionDenied(error)
         if not self.asset or not self.asset.is_active:
-            is_valid = False
             error = _('No asset or inactive asset')
-            return is_valid, error
+            raise PermissionDenied(error)
         if not self.account:
             error = _('No account')
             raise PermissionDenied(error)
@@ -160,6 +160,7 @@ class ConnectionToken(JMSOrgBaseModel):
             'remoteapplicationname:s': app,
             'alternate shell:s': app,
             'remoteapplicationcmdline:s': cmdline_b64,
+            'disableconnectionsharing:i': '1',
         }
         return options
 
@@ -172,7 +173,7 @@ class ConnectionToken(JMSOrgBaseModel):
         if not applet:
             return None
 
-        host_account = applet.select_host_account()
+        host_account = applet.select_host_account(self.user)
         if not host_account:
             raise JMSException({'error': 'No host account available'})
 
diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py
index 22545115f..7d3de1bb7 100644
--- a/apps/authentication/serializers/connection_token.py
+++ b/apps/authentication/serializers/connection_token.py
@@ -1,13 +1,16 @@
+from django.conf import settings
+from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
 from rest_framework import serializers
 
-from perms.serializers.permission import ActionChoicesField
-from orgs.mixins.serializers import OrgResourceModelSerializerMixin
 from common.serializers.fields import EncryptedField
+from orgs.mixins.serializers import OrgResourceModelSerializerMixin
+from perms.serializers.permission import ActionChoicesField
 from ..models import ConnectionToken
 
 __all__ = [
     'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer',
+    'ConnectionTokenUpdateSerializer',
 ]
 
 
@@ -25,13 +28,13 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
         fields_small = fields_mini + [
             'user', 'asset', 'account', 'input_username',
             'input_secret', 'connect_method', 'protocol', 'actions',
-            'is_active', 'from_ticket', 'from_ticket_info',
+            'is_active', 'is_reusable', 'from_ticket', 'from_ticket_info',
             'date_expired', 'date_created', 'date_updated', 'created_by',
             'updated_by', 'org_id', 'org_name',
         ]
         read_only_fields = [
             # 普通 Token 不支持指定 user
-            'user', 'expire_time', 'is_expired',
+            'user', 'expire_time', 'is_expired', 'date_expired',
             'user_display', 'asset_display',
         ]
         fields = fields_small + read_only_fields
@@ -57,6 +60,32 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
         return info
 
 
+class ConnectionTokenUpdateSerializer(ConnectionTokenSerializer):
+    class Meta(ConnectionTokenSerializer.Meta):
+        can_update_fields = ['is_reusable']
+        read_only_fields = list(set(ConnectionTokenSerializer.Meta.fields) - set(can_update_fields))
+
+    def _get_date_expired(self):
+        delta = self.instance.date_expired - self.instance.date_created
+        if delta.total_seconds() > 3600 * 24:
+            return self.instance.date_expired
+
+        seconds = settings.CONNECTION_TOKEN_EXPIRATION_MAX
+        return timezone.now() + timezone.timedelta(seconds=seconds)
+
+    @staticmethod
+    def validate_is_reusable(value):
+        if value and not settings.CONNECTION_TOKEN_REUSABLE:
+            raise serializers.ValidationError(_('Reusable connection token is not allowed, global setting not enabled'))
+        return value
+
+    def validate(self, attrs):
+        reusable = attrs.get('is_reusable', False)
+        if reusable:
+            attrs['date_expired'] = self._get_date_expired()
+        return attrs
+
+
 class SuperConnectionTokenSerializer(ConnectionTokenSerializer):
     class Meta(ConnectionTokenSerializer.Meta):
         read_only_fields = list(set(ConnectionTokenSerializer.Meta.read_only_fields) - {'user'})
diff --git a/apps/authentication/views/base.py b/apps/authentication/views/base.py
new file mode 100644
index 000000000..e98e41e70
--- /dev/null
+++ b/apps/authentication/views/base.py
@@ -0,0 +1,98 @@
+from functools import lru_cache
+
+from rest_framework.request import Request
+from django.utils.translation import ugettext_lazy as _
+from django.utils.module_loading import import_string
+from django.conf import settings
+from django.db.utils import IntegrityError
+from django.views import View
+
+from authentication import errors
+from authentication.mixins import AuthMixin
+from users.models import User
+from common.utils.django import reverse, get_object_or_none
+from common.utils import get_logger
+
+from .mixins import FlashMessageMixin
+
+
+logger = get_logger(__file__)
+
+
+class BaseLoginCallbackView(AuthMixin, FlashMessageMixin, View):
+
+    client_type_path = ''
+    client_auth_params = {}
+    user_type = ''
+    auth_backend = None
+    # 提示信息
+    msg_client_err = _('Error')
+    msg_user_not_bound_err = _('Error')
+    msg_not_found_user_from_client_err = _('Error')
+
+    def verify_state(self):
+        raise NotImplementedError
+
+    def get_verify_state_failed_response(self, redirect_uri):
+        raise NotImplementedError
+
+    @property
+    @lru_cache(maxsize=1)
+    def client(self):
+        if not all([self.client_type_path, self.client_auth_params]):
+            raise NotImplementedError
+        client_init = {k: getattr(settings, v) for k, v in self.client_auth_params.items()}
+        client_type = import_string(self.client_type_path)
+        return client_type(**client_init)
+
+    def create_user_if_not_exist(self, user_id, **kwargs):
+        user = None
+        user_attr = self.client.get_user_detail(user_id, **kwargs)
+        try:
+            user, create = User.objects.get_or_create(
+                username=user_attr['username'], defaults=user_attr
+            )
+            setattr(user, f'{self.user_type}_id', user_id)
+            if create:
+                setattr(user, 'source', self.user_type)
+            user.save()
+        except IntegrityError as err:
+            logger.error(f'{self.msg_client_err}: create user error: {err}')
+
+        if user is None:
+            title = self.msg_client_err
+            msg = _('If you have any question, please contact the administrator')
+            return user, (title, msg)
+
+        return user, None
+
+    def get(self, request: Request):
+        code = request.GET.get('code')
+        redirect_url = request.GET.get('redirect_url')
+        login_url = reverse('authentication:login')
+
+        if not self.verify_state():
+            return self.get_verify_state_failed_response(redirect_url)
+
+        user_id, other_info = self.client.get_user_id_by_code(code)
+        if not user_id:
+            # 正常流程不会出这个错误,hack 行为
+            err = self.msg_not_found_user_from_client_err
+            response = self.get_failed_response(login_url, title=err, msg=err)
+            return response
+
+        user = get_object_or_none(User, **{f'{self.user_type}_id': user_id})
+        if user is None:
+            user, err = self.create_user_if_not_exist(user_id, other_info=other_info)
+            if err is not None:
+                response = self.get_failed_response(login_url, title=err[0], msg=err[1])
+                return response
+
+        try:
+            self.check_oauth2_auth(user, getattr(settings, self.auth_backend))
+        except errors.AuthFailedError as e:
+            self.set_login_failed_mark()
+            msg = e.msg
+            response = self.get_failed_response(login_url, title=msg, msg=msg)
+            return response
+        return self.redirect_to_guard_view()
diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py
index e0e76f966..77cec63fd 100644
--- a/apps/authentication/views/dingtalk.py
+++ b/apps/authentication/views/dingtalk.py
@@ -16,21 +16,22 @@ from authentication.notifications import OAuthBindMessage
 from common.views.mixins import PermissionsMixin, UserConfirmRequiredExceptionMixin
 from common.permissions import UserConfirmation
 from common.sdk.im.dingtalk import URL, DingTalk
-from common.utils import FlashMessageUtil, get_logger
+from common.utils import get_logger
 from common.utils.common import get_request_ip
 from common.utils.django import get_object_or_none, reverse
 from common.utils.random import random_string
 from users.models import User
 from users.views import UserVerifyPasswordView
 
-from .mixins import METAMixin
+from .base import BaseLoginCallbackView
+from .mixins import METAMixin, FlashMessageMixin
 
 logger = get_logger(__file__)
 
 DINGTALK_STATE_SESSION_KEY = '_dingtalk_state'
 
 
-class DingTalkBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
+class DingTalkBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMessageMixin, View):
     def dispatch(self, request, *args, **kwargs):
         try:
             return super().dispatch(request, *args, **kwargs)
@@ -56,26 +57,6 @@ class DingTalkBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, Vie
         msg = _("The system configuration is incorrect. Please contact your administrator")
         return self.get_failed_response(redirect_uri, msg, msg)
 
-    @staticmethod
-    def get_success_response(redirect_url, title, msg):
-        message_data = {
-            'title': title,
-            'message': msg,
-            'interval': 5,
-            'redirect_url': redirect_url,
-        }
-        return FlashMessageUtil.gen_and_redirect_to(message_data)
-
-    @staticmethod
-    def get_failed_response(redirect_url, title, msg):
-        message_data = {
-            'title': title,
-            'error': msg,
-            'interval': 5,
-            'redirect_url': redirect_url,
-        }
-        return FlashMessageUtil.gen_and_redirect_to(message_data)
-
     def get_already_bound_response(self, redirect_url):
         msg = _('DingTalk is already bound')
         response = self.get_failed_response(redirect_url, msg, msg)
@@ -158,7 +139,7 @@ class DingTalkQRBindCallbackView(DingTalkQRMixin, View):
             appsecret=settings.DINGTALK_APPSECRET,
             agentid=settings.DINGTALK_AGENTID
         )
-        userid = dingtalk.get_userid_by_code(code)
+        userid, __ = dingtalk.get_user_id_by_code(code)
 
         if not userid:
             msg = _('DingTalk query user failed')
@@ -214,45 +195,20 @@ class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View):
         return HttpResponseRedirect(url)
 
 
-class DingTalkQRLoginCallbackView(AuthMixin, DingTalkQRMixin, View):
+class DingTalkQRLoginCallbackView(DingTalkQRMixin, BaseLoginCallbackView):
     permission_classes = (AllowAny,)
 
-    def get(self, request: HttpRequest):
-        code = request.GET.get('code')
-        redirect_url = request.GET.get('redirect_url')
-        login_url = reverse('authentication:login')
+    client_type_path = 'common.sdk.im.dingtalk.DingTalk'
+    client_auth_params = {
+        'appid': 'DINGTALK_APPKEY', 'appsecret': 'DINGTALK_APPSECRET',
+        'agentid': 'DINGTALK_AGENTID'
+    }
+    user_type = 'dingtalk'
+    auth_backend = 'AUTH_BACKEND_DINGTALK'
 
-        if not self.verify_state():
-            return self.get_verify_state_failed_response(redirect_url)
-
-        dingtalk = DingTalk(
-            appid=settings.DINGTALK_APPKEY,
-            appsecret=settings.DINGTALK_APPSECRET,
-            agentid=settings.DINGTALK_AGENTID
-        )
-        userid = dingtalk.get_userid_by_code(code)
-        if not userid:
-            # 正常流程不会出这个错误,hack 行为
-            msg = _('Failed to get user from DingTalk')
-            response = self.get_failed_response(login_url, title=msg, msg=msg)
-            return response
-
-        user = get_object_or_none(User, dingtalk_id=userid)
-        if user is None:
-            title = _('DingTalk is not bound')
-            msg = _('Please login with a password and then bind the DingTalk')
-            response = self.get_failed_response(login_url, title=title, msg=msg)
-            return response
-
-        try:
-            self.check_oauth2_auth(user, settings.AUTH_BACKEND_DINGTALK)
-        except errors.AuthFailedError as e:
-            self.set_login_failed_mark()
-            msg = e.msg
-            response = self.get_failed_response(login_url, title=msg, msg=msg)
-            return response
-
-        return self.redirect_to_guard_view()
+    msg_client_err = _('DingTalk Error')
+    msg_user_not_bound_err = _('DingTalk is not bound')
+    msg_not_found_user_from_client_err = _('Failed to get user from DingTalk')
 
 
 class DingTalkOAuthLoginView(DingTalkOAuthMixin, View):
@@ -284,7 +240,7 @@ class DingTalkOAuthLoginCallbackView(AuthMixin, DingTalkOAuthMixin, View):
             appsecret=settings.DINGTALK_APPSECRET,
             agentid=settings.DINGTALK_AGENTID
         )
-        userid = dingtalk.get_userid_by_code(code)
+        userid, __ = dingtalk.get_user_id_by_code(code)
         if not userid:
             # 正常流程不会出这个错误,hack 行为
             msg = _('Failed to get user from DingTalk')
diff --git a/apps/authentication/views/feishu.py b/apps/authentication/views/feishu.py
index e9734b170..62660764a 100644
--- a/apps/authentication/views/feishu.py
+++ b/apps/authentication/views/feishu.py
@@ -9,26 +9,26 @@ from django.views import View
 from rest_framework.exceptions import APIException
 from rest_framework.permissions import AllowAny, IsAuthenticated
 
-from authentication import errors
 from authentication.const import ConfirmType
-from authentication.mixins import AuthMixin
 from authentication.notifications import OAuthBindMessage
 from common.views.mixins import PermissionsMixin, UserConfirmRequiredExceptionMixin
 from common.permissions import UserConfirmation
 from common.sdk.im.feishu import URL, FeiShu
-from common.utils import FlashMessageUtil, get_logger
+from common.utils import get_logger
 from common.utils.common import get_request_ip
-from common.utils.django import get_object_or_none, reverse
+from common.utils.django import reverse
 from common.utils.random import random_string
-from users.models import User
 from users.views import UserVerifyPasswordView
 
+from .base import BaseLoginCallbackView
+from .mixins import FlashMessageMixin
+
 logger = get_logger(__file__)
 
 FEISHU_STATE_SESSION_KEY = '_feishu_state'
 
 
-class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
+class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMessageMixin, View):
     def dispatch(self, request, *args, **kwargs):
         try:
             return super().dispatch(request, *args, **kwargs)
@@ -63,26 +63,6 @@ class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
         url = URL().authen + '?' + urlencode(params)
         return url
 
-    @staticmethod
-    def get_success_response(redirect_url, title, msg):
-        message_data = {
-            'title': title,
-            'message': msg,
-            'interval': 5,
-            'redirect_url': redirect_url,
-        }
-        return FlashMessageUtil.gen_and_redirect_to(message_data)
-
-    @staticmethod
-    def get_failed_response(redirect_url, title, msg):
-        message_data = {
-            'title': title,
-            'error': msg,
-            'interval': 5,
-            'redirect_url': redirect_url,
-        }
-        return FlashMessageUtil.gen_and_redirect_to(message_data)
-
     def get_already_bound_response(self, redirect_url):
         msg = _('FeiShu is already bound')
         response = self.get_failed_response(redirect_url, msg, msg)
@@ -93,7 +73,6 @@ class FeiShuQRBindView(FeiShuQRMixin, View):
     permission_classes = (IsAuthenticated, UserConfirmation.require(ConfirmType.ReLogin))
 
     def get(self, request: HttpRequest):
-        user = request.user
         redirect_url = request.GET.get('redirect_url')
 
         redirect_uri = reverse('authentication:feishu-qr-bind-callback', external=True)
@@ -123,7 +102,7 @@ class FeiShuQRBindCallbackView(FeiShuQRMixin, View):
             app_id=settings.FEISHU_APP_ID,
             app_secret=settings.FEISHU_APP_SECRET
         )
-        user_id = feishu.get_user_id_by_code(code)
+        user_id, __ = feishu.get_user_id_by_code(code)
 
         if not user_id:
             msg = _('FeiShu query user failed')
@@ -176,41 +155,15 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
         return HttpResponseRedirect(url)
 
 
-class FeiShuQRLoginCallbackView(AuthMixin, FeiShuQRMixin, View):
+class FeiShuQRLoginCallbackView(FeiShuQRMixin, BaseLoginCallbackView):
     permission_classes = (AllowAny,)
 
-    def get(self, request: HttpRequest):
-        code = request.GET.get('code')
-        redirect_url = request.GET.get('redirect_url')
-        login_url = reverse('authentication:login')
+    client_type_path = 'common.sdk.im.feishu.FeiShu'
+    client_auth_params = {'app_id': 'FEISHU_APP_ID', 'app_secret': 'FEISHU_APP_SECRET'}
+    user_type = 'feishu'
+    auth_backend = 'AUTH_BACKEND_FEISHU'
 
-        if not self.verify_state():
-            return self.get_verify_state_failed_response(redirect_url)
+    msg_client_err = _('FeiShu Error')
+    msg_user_not_bound_err = _('FeiShu is not bound')
+    msg_not_found_user_from_client_err = _('Failed to get user from FeiShu')
 
-        feishu = FeiShu(
-            app_id=settings.FEISHU_APP_ID,
-            app_secret=settings.FEISHU_APP_SECRET
-        )
-        user_id = feishu.get_user_id_by_code(code)
-        if not user_id:
-            # 正常流程不会出这个错误,hack 行为
-            msg = _('Failed to get user from FeiShu')
-            response = self.get_failed_response(login_url, title=msg, msg=msg)
-            return response
-
-        user = get_object_or_none(User, feishu_id=user_id)
-        if user is None:
-            title = _('FeiShu is not bound')
-            msg = _('Please login with a password and then bind the FeiShu')
-            response = self.get_failed_response(login_url, title=title, msg=msg)
-            return response
-
-        try:
-            self.check_oauth2_auth(user, settings.AUTH_BACKEND_FEISHU)
-        except errors.AuthFailedError as e:
-            self.set_login_failed_mark()
-            msg = e.msg
-            response = self.get_failed_response(login_url, title=msg, msg=msg)
-            return response
-
-        return self.redirect_to_guard_view()
diff --git a/apps/authentication/views/mixins.py b/apps/authentication/views/mixins.py
index 3571dfac7..d56206dcd 100644
--- a/apps/authentication/views/mixins.py
+++ b/apps/authentication/views/mixins.py
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 #
+from common.utils import FlashMessageUtil
+
 
 class METAMixin:
     def get_next_url_from_meta(self):
@@ -10,3 +12,16 @@ class METAMixin:
         if len(next_url_item) > 1:
             next_url = next_url_item[-1]
         return next_url
+
+
+class FlashMessageMixin:
+    @staticmethod
+    def get_response(redirect_url, title, msg, m_type='message'):
+        message_data = {'title': title, 'interval': 5, 'redirect_url': redirect_url, m_type: msg}
+        return FlashMessageUtil.gen_and_redirect_to(message_data)
+
+    def get_success_response(self, redirect_url, title, msg):
+        return self.get_response(redirect_url, title, msg)
+
+    def get_failed_response(self, redirect_url, title, msg):
+        return self.get_response(redirect_url, title, msg, 'error')
diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py
index c764c2138..e127dbeee 100644
--- a/apps/authentication/views/wecom.py
+++ b/apps/authentication/views/wecom.py
@@ -10,7 +10,7 @@ from rest_framework.exceptions import APIException
 
 from users.models import User
 from users.views import UserVerifyPasswordView
-from common.utils import get_logger, FlashMessageUtil
+from common.utils import get_logger
 from common.utils.random import random_string
 from common.utils.django import reverse, get_object_or_none
 from common.sdk.im.wecom import URL
@@ -22,14 +22,16 @@ from authentication import errors
 from authentication.mixins import AuthMixin
 from authentication.const import ConfirmType
 from authentication.notifications import OAuthBindMessage
-from .mixins import METAMixin
+
+from .base import BaseLoginCallbackView
+from .mixins import METAMixin, FlashMessageMixin
 
 logger = get_logger(__file__)
 
 WECOM_STATE_SESSION_KEY = '_wecom_state'
 
 
-class WeComBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
+class WeComBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMessageMixin, View):
     def dispatch(self, request, *args, **kwargs):
         try:
             return super().dispatch(request, *args, **kwargs)
@@ -55,26 +57,6 @@ class WeComBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, View):
         msg = _("The system configuration is incorrect. Please contact your administrator")
         return self.get_failed_response(redirect_uri, msg, msg)
 
-    @staticmethod
-    def get_success_response(redirect_url, title, msg):
-        message_data = {
-            'title': title,
-            'message': msg,
-            'interval': 5,
-            'redirect_url': redirect_url,
-        }
-        return FlashMessageUtil.gen_and_redirect_to(message_data)
-
-    @staticmethod
-    def get_failed_response(redirect_url, title, msg):
-        message_data = {
-            'title': title,
-            'error': msg,
-            'interval': 5,
-            'redirect_url': redirect_url,
-        }
-        return FlashMessageUtil.gen_and_redirect_to(message_data)
-
     def get_already_bound_response(self, redirect_url):
         msg = _('WeCom is already bound')
         response = self.get_failed_response(redirect_url, msg, msg)
@@ -208,45 +190,17 @@ class WeComQRLoginView(WeComQRMixin, METAMixin, View):
         return HttpResponseRedirect(url)
 
 
-class WeComQRLoginCallbackView(AuthMixin, WeComQRMixin, View):
+class WeComQRLoginCallbackView(WeComQRMixin, BaseLoginCallbackView):
     permission_classes = (AllowAny,)
 
-    def get(self, request: HttpRequest):
-        code = request.GET.get('code')
-        redirect_url = request.GET.get('redirect_url')
-        login_url = reverse('authentication:login')
+    client_type_path = 'common.sdk.im.wecom.WeCom'
+    client_auth_params = {'corpid': 'WECOM_CORPID', 'corpsecret': 'WECOM_SECRET', 'agentid': 'WECOM_AGENTID'}
+    user_type = 'wecom'
+    auth_backend = 'AUTH_BACKEND_WECOM'
 
-        if not self.verify_state():
-            return self.get_verify_state_failed_response(redirect_url)
-
-        wecom = WeCom(
-            corpid=settings.WECOM_CORPID,
-            corpsecret=settings.WECOM_SECRET,
-            agentid=settings.WECOM_AGENTID
-        )
-        wecom_userid, __ = wecom.get_user_id_by_code(code)
-        if not wecom_userid:
-            # 正常流程不会出这个错误,hack 行为
-            msg = _('Failed to get user from WeCom')
-            response = self.get_failed_response(login_url, title=msg, msg=msg)
-            return response
-
-        user = get_object_or_none(User, wecom_id=wecom_userid)
-        if user is None:
-            title = _('WeCom is not bound')
-            msg = _('Please login with a password and then bind the WeCom')
-            response = self.get_failed_response(login_url, title=title, msg=msg)
-            return response
-
-        try:
-            self.check_oauth2_auth(user, settings.AUTH_BACKEND_WECOM)
-        except errors.AuthFailedError as e:
-            self.set_login_failed_mark()
-            msg = e.msg
-            response = self.get_failed_response(login_url, title=msg, msg=msg)
-            return response
-
-        return self.redirect_to_guard_view()
+    msg_client_err = _('WeCom Error')
+    msg_user_not_bound_err = _('WeCom is not bound')
+    msg_not_found_user_from_client_err = _('Failed to get user from WeCom')
 
 
 class WeComOAuthLoginView(WeComOAuthMixin, View):
diff --git a/apps/common/drf/renders/base.py b/apps/common/drf/renders/base.py
index 5b2aa5612..a9b3b5c8a 100644
--- a/apps/common/drf/renders/base.py
+++ b/apps/common/drf/renders/base.py
@@ -1,6 +1,10 @@
 import abc
+import io
+import re
 from datetime import datetime
 
+import pyzipper
+from django.utils.translation import ugettext_lazy as _
 from rest_framework import serializers
 from rest_framework.renderers import BaseRenderer
 from rest_framework.utils import encoders, json
@@ -181,8 +185,35 @@ class BaseFileRenderer(BaseRenderer):
             self.write_rows(rows)
             self.after_render()
             value = self.get_rendered_value()
+            if getattr(view, 'export_as_zip', False) and self.template == 'export':
+                value = self.compress_into_zip_file(value, request, response)
         except Exception as e:
             logger.debug(e, exc_info=True)
             value = 'Render error! ({})'.format(self.media_type).encode('utf-8')
             return value
         return value
+
+    def compress_into_zip_file(self, value, request, response):
+        filename_pattern = re.compile(r'filename="([^"]+)"')
+        content_disposition = response['Content-Disposition']
+        match = filename_pattern.search(content_disposition)
+        filename = match.group(1)
+        response['Content-Disposition'] = content_disposition.replace(self.format, 'zip')
+
+        contents_io = io.BytesIO()
+        secret_key = request.user.secret_key
+        if not secret_key:
+            content = _("{} - The encryption password has not been set - "
+                        "please go to personal information -> file encryption password "
+                        "to set the encryption password").format(request.user.name)
+
+            response['Content-Disposition'] = content_disposition.replace(self.format, 'txt')
+            contents_io.write(content.encode('utf-8'))
+            return contents_io.getvalue()
+
+        with pyzipper.AESZipFile(
+                contents_io, 'w', compression=pyzipper.ZIP_LZMA, encryption=pyzipper.WZ_AES
+        ) as zf:
+            zf.setpassword(secret_key.encode('utf8'))
+            zf.writestr(filename, value)
+        return contents_io.getvalue()
diff --git a/apps/common/management/commands/services/services/flower.py b/apps/common/management/commands/services/services/flower.py
index bb1452827..94dd8e2f0 100644
--- a/apps/common/management/commands/services/services/flower.py
+++ b/apps/common/management/commands/services/services/flower.py
@@ -11,7 +11,7 @@ class FlowerService(BaseService):
 
     @property
     def db_file(self):
-        return os.path.join(BASE_DIR, 'data', 'flower')
+        return os.path.join(BASE_DIR, 'data', 'flower.db')
 
     @property
     def cmd(self):
diff --git a/apps/common/sdk/im/dingtalk/__init__.py b/apps/common/sdk/im/dingtalk/__init__.py
index d41e73221..4f54e1ea2 100644
--- a/apps/common/sdk/im/dingtalk/__init__.py
+++ b/apps/common/sdk/im/dingtalk/__init__.py
@@ -5,6 +5,7 @@ import base64
 from common.utils import get_logger
 from common.sdk.im.utils import digest, as_request
 from common.sdk.im.mixin import BaseRequest
+from users.utils import construct_user_email
 
 logger = get_logger(__file__)
 
@@ -35,6 +36,7 @@ class URL:
     SEND_MESSAGE = 'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2'
     GET_SEND_MSG_PROGRESS = 'https://oapi.dingtalk.com/topapi/message/corpconversation/getsendprogress'
     GET_USERID_BY_UNIONID = 'https://oapi.dingtalk.com/topapi/user/getbyunionid'
+    GET_USER_INFO_BY_USER_ID = 'https://oapi.dingtalk.com/topapi/v2/user/get'
 
 
 class DingTalkRequests(BaseRequest):
@@ -129,11 +131,11 @@ class DingTalk:
         data = self._request.post(URL.GET_USER_INFO_BY_CODE, json=body, with_sign=True)
         return data['user_info']
 
-    def get_userid_by_code(self, code):
+    def get_user_id_by_code(self, code):
         user_info = self.get_userinfo_bycode(code)
         unionid = user_info['unionid']
         userid = self.get_userid_by_unionid(unionid)
-        return userid
+        return userid, None
 
     def get_userid_by_unionid(self, unionid):
         body = {
@@ -195,3 +197,18 @@ class DingTalk:
 
         data = self._request.post(URL.GET_SEND_MSG_PROGRESS, json=body, with_token=True)
         return data
+
+    def get_user_detail(self, user_id, **kwargs):
+        # https://open.dingtalk.com/document/orgapp/query-user-details
+        body = {'userid': user_id}
+        data = self._request.post(
+            URL.GET_USER_INFO_BY_USER_ID, json=body, with_token=True
+        )
+        data = data['result']
+        username = user_id
+        name = data.get('name', username)
+        email = data.get('email') or data.get('org_email')
+        email = construct_user_email(username, email)
+        return {
+            'username': username, 'name': name, 'email': email
+        }
diff --git a/apps/common/sdk/im/feishu/__init__.py b/apps/common/sdk/im/feishu/__init__.py
index cb01b66da..087a2fb88 100644
--- a/apps/common/sdk/im/feishu/__init__.py
+++ b/apps/common/sdk/im/feishu/__init__.py
@@ -1,9 +1,9 @@
 import json
 
-from django.utils.translation import ugettext_lazy as _
 from rest_framework.exceptions import APIException
 
 from django.conf import settings
+from users.utils import construct_user_email
 from common.utils.common import get_logger
 from common.sdk.im.utils import digest
 from common.sdk.im.mixin import RequestMixin, BaseRequest
@@ -37,6 +37,9 @@ class URL:
     def send_message(self):
         return f'{self.host}/open-apis/im/v1/messages'
 
+    def get_user_detail(self, user_id):
+        return f'{self.host}/open-apis/contact/v3/users/{user_id}'
+
 
 class ErrorCode:
     INVALID_APP_ACCESS_TOKEN = 99991664
@@ -106,7 +109,7 @@ class FeiShu(RequestMixin):
         data = self._requests.post(URL().get_user_info_by_code, json=body, check_errcode_is_0=False)
 
         self._requests.check_errcode_is_0(data)
-        return data['data']['user_id']
+        return data['data']['user_id'], data['data']
 
     def send_text(self, user_ids, msg):
         params = {
@@ -130,3 +133,15 @@ class FeiShu(RequestMixin):
                 logger.exception(e)
                 invalid_users.append(user_id)
         return invalid_users
+
+    @staticmethod
+    def get_user_detail(user_id, **kwargs):
+        # get_user_id_by_code 已经返回个人信息,这里直接解析
+        data = kwargs['other_info']
+        username = user_id
+        name = data.get('name', username)
+        email = data.get('email') or data.get('enterprise_email')
+        email = construct_user_email(username, email)
+        return {
+            'username': username, 'name': name, 'email': email
+        }
diff --git a/apps/common/sdk/im/wecom/__init__.py b/apps/common/sdk/im/wecom/__init__.py
index bc925508e..5d2d6a4c1 100644
--- a/apps/common/sdk/im/wecom/__init__.py
+++ b/apps/common/sdk/im/wecom/__init__.py
@@ -3,8 +3,9 @@ from typing import Iterable, AnyStr
 from django.utils.translation import ugettext_lazy as _
 from rest_framework.exceptions import APIException
 
+from users.utils import construct_user_email
 from common.utils.common import get_logger
-from common.sdk.im.utils import digest, DictWrapper, update_values, set_default
+from common.sdk.im.utils import digest, update_values
 from common.sdk.im.mixin import RequestMixin, BaseRequest
 
 logger = get_logger(__name__)
@@ -151,10 +152,7 @@ class WeCom(RequestMixin):
 
     def get_user_id_by_code(self, code):
         # # https://open.work.weixin.qq.com/api/doc/90000/90135/91437
-
-        params = {
-            'code': code,
-        }
+        params = {'code': code}
         data = self._requests.get(URL.GET_USER_ID_BY_CODE, params=params, check_errcode_is_0=False)
 
         errcode = data['errcode']
@@ -175,12 +173,15 @@ class WeCom(RequestMixin):
             logger.error(f'WeCom response 200 but get field from json error: fields=UserId|OpenId')
             raise WeComError
 
-    def get_user_detail(self, id):
+    def get_user_detail(self, user_id, **kwargs):
         # https://open.work.weixin.qq.com/api/doc/90000/90135/90196
-
-        params = {
-            'userid': id,
+        params = {'userid': user_id}
+        data = self._requests.get(URL.GET_USER_DETAIL, params)
+        username = data.get('userid')
+        name = data.get('name', username)
+        email = data.get('email') or data.get('biz_mail')
+        email = construct_user_email(username, email)
+        return {
+            'username': username, 'name': name, 'email': email
         }
 
-        data = self._requests.get(URL.GET_USER_DETAIL, params)
-        return data
diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py
index 51b97b8b6..767645c88 100644
--- a/apps/common/utils/encode.py
+++ b/apps/common/utils/encode.py
@@ -175,6 +175,8 @@ def _parse_ssh_private_key(text, password=None):
                 dsa.DSAPrivateKey,
                 ed25519.Ed25519PrivateKey,
     """
+    if not bool(password):
+        password = None
     if isinstance(text, str):
         try:
             text = text.encode("utf-8")
diff --git a/apps/common/utils/http.py b/apps/common/utils/http.py
index b684f004a..baf741407 100644
--- a/apps/common/utils/http.py
+++ b/apps/common/utils/http.py
@@ -45,3 +45,7 @@ def get_remote_addr(request):
 
 def is_true(value):
     return value in BooleanField.TRUE_VALUES
+
+
+def is_false(value):
+    return value in BooleanField.FALSE_VALUES
diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py
index 343224675..18872058a 100644
--- a/apps/jumpserver/conf.py
+++ b/apps/jumpserver/conf.py
@@ -229,7 +229,9 @@ class Config(dict):
         'SESSION_COOKIE_AGE': 3600 * 24,
         'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
         'LOGIN_URL': reverse_lazy('authentication:login'),
-        'CONNECTION_TOKEN_EXPIRATION': 5 * 60,
+        'CONNECTION_TOKEN_EXPIRATION': 5 * 60,  # 默认
+        'CONNECTION_TOKEN_EXPIRATION_MAX': 60 * 60 * 24 * 30,  # 最大
+        'CONNECTION_TOKEN_REUSABLE': False,
 
         # Custom Config
         'AUTH_CUSTOM': False,
diff --git a/apps/jumpserver/rewriting/storage/permissions.py b/apps/jumpserver/rewriting/storage/permissions.py
index 492a6d06f..51b1c9c40 100644
--- a/apps/jumpserver/rewriting/storage/permissions.py
+++ b/apps/jumpserver/rewriting/storage/permissions.py
@@ -2,6 +2,7 @@
 
 path_perms_map = {
     'xpack': '*',
+    'settings': '*',
     'replay': 'default',
     'applets': 'terminal.view_applet',
     'playbooks': 'ops.view_playbook'
diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py
index 62ba143ac..7312bd7eb 100644
--- a/apps/jumpserver/settings/custom.py
+++ b/apps/jumpserver/settings/custom.py
@@ -131,6 +131,9 @@ TICKETS_ENABLED = CONFIG.TICKETS_ENABLED
 REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED
 
 CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED
+CONNECTION_TOKEN_REUSABLE = CONFIG.CONNECTION_TOKEN_REUSABLE
+CONNECTION_TOKEN_EXPIRATION_MAX = CONFIG.CONNECTION_TOKEN_EXPIRATION_MAX
+
 FORGOT_PASSWORD_URL = CONFIG.FORGOT_PASSWORD_URL
 
 # 自定义默认组织名
diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo
index 7d2f91c61..04807dc75 100644
--- a/apps/locale/ja/LC_MESSAGES/django.mo
+++ b/apps/locale/ja/LC_MESSAGES/django.mo
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:24858bf247f7af58abda5adb5be733b7b995df2e26de7c91caf43f7aa0dd3be0
-size 139654
+oid sha256:523a93e9703e62c39440d2e172c96fea7d8d04965cab43095fc8a378d157bf59
+size 141798
diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po
index c113ce8ee..722d482d8 100644
--- a/apps/locale/ja/LC_MESSAGES/django.po
+++ b/apps/locale/ja/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-04-21 14:05+0800\n"
+"POT-Creation-Date: 2023-05-16 18:32+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME \n"
 "Language-Team: LANGUAGE \n"
@@ -70,7 +70,7 @@ msgstr "ローカル"
 msgid "Collected"
 msgstr "集めました"
 
-#: accounts/const/account.py:21 accounts/serializers/account/account.py:25
+#: accounts/const/account.py:21 accounts/serializers/account/account.py:26
 #: settings/serializers/auth/sms.py:75
 msgid "Template"
 msgstr "テンプレート"
@@ -87,7 +87,7 @@ msgstr "更新"
 #: accounts/const/account.py:27
 #: accounts/serializers/automations/change_secret.py:156 audits/const.py:53
 #: audits/signal_handlers/activity_log.py:33 common/const/choices.py:19
-#: ops/const.py:58 terminal/const.py:60 xpack/plugins/cloud/const.py:41
+#: ops/const.py:58 terminal/const.py:61 xpack/plugins/cloud/const.py:41
 msgid "Failed"
 msgstr "失敗しました"
 
@@ -179,14 +179,14 @@ msgstr "作成してプッシュ"
 msgid "Only create"
 msgstr "作成のみ"
 
-#: accounts/models/account.py:47
+#: accounts/models/account.py:49
 #: accounts/models/automations/gather_account.py:16
-#: accounts/serializers/account/account.py:199
-#: accounts/serializers/account/account.py:232
+#: accounts/serializers/account/account.py:201
+#: accounts/serializers/account/account.py:234
 #: accounts/serializers/account/gathered_account.py:10
 #: accounts/serializers/automations/change_secret.py:112
 #: accounts/serializers/automations/change_secret.py:132
-#: acls/models/base.py:100 acls/serializers/base.py:56
+#: acls/models/base.py:100 acls/serializers/base.py:76
 #: assets/models/asset/common.py:92 assets/models/asset/common.py:306
 #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19
 #: assets/serializers/label.py:27 audits/models.py:48
@@ -198,29 +198,32 @@ msgstr "作成のみ"
 msgid "Asset"
 msgstr "資産"
 
-#: accounts/models/account.py:51 accounts/serializers/account/account.py:204
+#: accounts/models/account.py:53 accounts/models/account.py:113
+#: accounts/serializers/account/account.py:206
+#: accounts/serializers/account/account.py:244
+#: accounts/serializers/account/template.py:16
 #: authentication/serializers/connect_token_secret.py:49
 msgid "Su from"
 msgstr "から切り替え"
 
-#: accounts/models/account.py:53 settings/serializers/auth/cas.py:20
+#: accounts/models/account.py:55 settings/serializers/auth/cas.py:20
 #: settings/serializers/auth/feishu.py:20 terminal/models/applet/applet.py:29
 msgid "Version"
 msgstr "バージョン"
 
-#: accounts/models/account.py:55 accounts/serializers/account/account.py:200
-#: users/models/user.py:768
+#: accounts/models/account.py:57 accounts/serializers/account/account.py:202
+#: users/models/user.py:778
 msgid "Source"
 msgstr "ソース"
 
-#: accounts/models/account.py:56
+#: accounts/models/account.py:58
 msgid "Source ID"
 msgstr "ソース ID"
 
-#: accounts/models/account.py:59
+#: accounts/models/account.py:61
 #: accounts/serializers/automations/change_secret.py:113
 #: accounts/serializers/automations/change_secret.py:133
-#: acls/models/base.py:102 acls/serializers/base.py:57
+#: acls/models/base.py:102 acls/serializers/base.py:77
 #: assets/serializers/asset/common.py:125 assets/serializers/gateway.py:28
 #: audits/models.py:49 ops/models/base.py:18
 #: perms/models/asset_permission.py:70 perms/serializers/permission.py:39
@@ -229,35 +232,35 @@ msgstr "ソース ID"
 msgid "Account"
 msgstr "アカウント"
 
-#: accounts/models/account.py:65
+#: accounts/models/account.py:67
 msgid "Can view asset account secret"
 msgstr "資産アカウントの秘密を表示できます"
 
-#: accounts/models/account.py:66
+#: accounts/models/account.py:68
 msgid "Can view asset history account"
 msgstr "資産履歴アカウントを表示できます"
 
-#: accounts/models/account.py:67
+#: accounts/models/account.py:69
 msgid "Can view asset history account secret"
 msgstr "資産履歴アカウントパスワードを表示できます"
 
-#: accounts/models/account.py:68
+#: accounts/models/account.py:70
 msgid "Can verify account"
 msgstr "アカウントを確認できます"
 
-#: accounts/models/account.py:69
+#: accounts/models/account.py:71
 msgid "Can push account"
 msgstr "アカウントをプッシュできます"
 
-#: accounts/models/account.py:110
+#: accounts/models/account.py:117
 msgid "Account template"
 msgstr "アカウント テンプレート"
 
-#: accounts/models/account.py:115
+#: accounts/models/account.py:122
 msgid "Can view asset account template secret"
 msgstr "アセット アカウント テンプレートのパスワードを表示できます"
 
-#: accounts/models/account.py:116
+#: accounts/models/account.py:123
 msgid "Can change asset account template secret"
 msgstr "アセット アカウント テンプレートのパスワードを変更できます"
 
@@ -276,7 +279,8 @@ msgstr "アカウントバックアップ計画"
 #: accounts/models/automations/backup_account.py:83
 #: assets/models/automations/base.py:115 audits/models.py:55
 #: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:192
-#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:109
+#: ops/templates/ops/celery_task_log.html:75
+#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:137
 #: terminal/models/session/session.py:45
 #: tickets/models/ticket/apply_application.py:30
 #: tickets/models/ticket/apply_asset.py:19
@@ -309,7 +313,7 @@ msgstr "理由"
 #: accounts/models/automations/backup_account.py:99
 #: accounts/serializers/automations/change_secret.py:111
 #: accounts/serializers/automations/change_secret.py:134
-#: ops/serializers/job.py:72 terminal/serializers/session.py:45
+#: ops/serializers/job.py:56 terminal/serializers/session.py:45
 msgid "Is success"
 msgstr "成功は"
 
@@ -354,7 +358,7 @@ msgid "Can add push account execution"
 msgstr "プッシュ アカウントの作成の実行"
 
 #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36
-#: accounts/serializers/account/account.py:394
+#: accounts/serializers/account/account.py:412
 #: accounts/serializers/account/base.py:16
 #: accounts/serializers/automations/change_secret.py:46
 #: authentication/serializers/connect_token_secret.py:41
@@ -399,13 +403,14 @@ msgstr "開始日"
 #: accounts/models/automations/change_secret.py:91
 #: assets/models/automations/base.py:116 ops/models/base.py:56
 #: ops/models/celery.py:64 ops/models/job.py:193
-#: terminal/models/applet/host.py:110
+#: terminal/models/applet/host.py:138
 msgid "Date finished"
 msgstr "終了日"
 
 #: accounts/models/automations/change_secret.py:93
-#: accounts/serializers/account/account.py:234 assets/const/automation.py:8
-#: common/const/choices.py:20
+#: accounts/serializers/account/account.py:236 assets/const/automation.py:8
+#: authentication/views/base.py:29 authentication/views/base.py:30
+#: authentication/views/base.py:31 common/const/choices.py:20
 msgid "Error"
 msgstr "間違い"
 
@@ -423,13 +428,13 @@ msgstr "最終ログイン日"
 
 #: accounts/models/automations/gather_account.py:17
 #: accounts/models/automations/push_account.py:15 accounts/models/base.py:34
-#: acls/serializers/base.py:18 acls/serializers/base.py:49
+#: acls/serializers/base.py:19 acls/serializers/base.py:50
 #: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25
 #: authentication/forms.py:27 authentication/models/temp_token.py:9
 #: authentication/templates/authentication/_msg_different_city.html:9
 #: authentication/templates/authentication/_msg_oauth_bind.html:9
 #: users/forms/profile.py:32 users/forms/profile.py:112
-#: users/models/user.py:715 users/templates/users/_msg_user_created.html:12
+#: users/models/user.py:725 users/templates/users/_msg_user_created.html:12
 #: xpack/plugins/cloud/serializers/account_attrs.py:26
 msgid "Username"
 msgstr "ユーザー名"
@@ -456,8 +461,8 @@ msgid "Triggers"
 msgstr "トリガー方式"
 
 #: accounts/models/automations/push_account.py:16 acls/models/base.py:81
-#: acls/serializers/base.py:81 acls/serializers/login_acl.py:26
-#: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82
+#: acls/serializers/base.py:57 assets/models/cmd_filter.py:81
+#: audits/models.py:65 audits/serializers.py:82
 #: authentication/serializers/connect_token_secret.py:108
 #: authentication/templates/authentication/_access_key_modal.html:34
 msgid "Action"
@@ -472,14 +477,14 @@ msgid "Verify asset account"
 msgstr "アカウントの確認"
 
 #: accounts/models/base.py:33 acls/models/base.py:75
-#: acls/models/command_acl.py:21 acls/serializers/base.py:34
+#: acls/models/command_acl.py:21 acls/serializers/base.py:35
 #: applications/models.py:9 assets/models/_user.py:22
 #: assets/models/asset/common.py:90 assets/models/asset/common.py:123
 #: assets/models/cmd_filter.py:21 assets/models/domain.py:18
 #: assets/models/group.py:20 assets/models/label.py:18
-#: assets/models/platform.py:13 assets/models/platform.py:89
+#: assets/models/platform.py:13 assets/models/platform.py:81
 #: assets/serializers/asset/common.py:145 assets/serializers/platform.py:92
-#: assets/serializers/platform.py:193
+#: assets/serializers/platform.py:194
 #: authentication/serializers/connect_token_secret.py:102 ops/mixin.py:21
 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57
 #: ops/models/job.py:92 ops/models/playbook.py:23 ops/serializers/job.py:20
@@ -489,7 +494,7 @@ msgstr "アカウントの確認"
 #: terminal/models/component/endpoint.py:90
 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:15
 #: terminal/models/component/terminal.py:84 users/forms/profile.py:33
-#: users/models/group.py:13 users/models/user.py:717
+#: users/models/group.py:13 users/models/user.py:727
 #: xpack/plugins/cloud/models.py:28
 msgid "Name"
 msgstr "名前"
@@ -547,28 +552,28 @@ msgstr ""
 "{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人"
 "情報にアクセスしてください-> ファイル暗号化パスワードを設定してください"
 
-#: accounts/serializers/account/account.py:28
+#: accounts/serializers/account/account.py:29
 msgid "Push now"
 msgstr "今すぐプッシュ"
 
-#: accounts/serializers/account/account.py:35
+#: accounts/serializers/account/account.py:36
 msgid "Exist policy"
 msgstr "アカウントの存在ポリシー"
 
-#: accounts/serializers/account/account.py:179 applications/models.py:11
-#: assets/models/label.py:21 assets/models/platform.py:90
+#: accounts/serializers/account/account.py:181 applications/models.py:11
+#: assets/models/label.py:21 assets/models/platform.py:82
 #: assets/serializers/asset/common.py:121 assets/serializers/cagegory.py:8
-#: assets/serializers/platform.py:110 assets/serializers/platform.py:194
+#: assets/serializers/platform.py:110 assets/serializers/platform.py:195
 #: perms/serializers/user_permission.py:26 settings/models.py:35
 #: tickets/models/ticket/apply_application.py:13
 msgid "Category"
 msgstr "カテゴリ"
 
-#: accounts/serializers/account/account.py:180
+#: accounts/serializers/account/account.py:182
 #: accounts/serializers/automations/base.py:54 acls/models/command_acl.py:24
 #: acls/serializers/command_acl.py:18 applications/models.py:14
 #: assets/models/_user.py:50 assets/models/automations/base.py:20
-#: assets/models/cmd_filter.py:74 assets/models/platform.py:91
+#: assets/models/cmd_filter.py:74 assets/models/platform.py:83
 #: assets/serializers/asset/common.py:122 assets/serializers/platform.py:94
 #: assets/serializers/platform.py:109 audits/serializers.py:48
 #: authentication/serializers/connect_token_secret.py:115 ops/models/job.py:103
@@ -583,27 +588,27 @@ msgstr "カテゴリ"
 msgid "Type"
 msgstr "タイプ"
 
-#: accounts/serializers/account/account.py:195
+#: accounts/serializers/account/account.py:197
 msgid "Asset not found"
 msgstr "資産が存在しません"
 
-#: accounts/serializers/account/account.py:201
+#: accounts/serializers/account/account.py:203
 #: accounts/serializers/account/base.py:64
 msgid "Has secret"
 msgstr "エスクローされたパスワード"
 
-#: accounts/serializers/account/account.py:233 ops/models/celery.py:60
+#: accounts/serializers/account/account.py:235 ops/models/celery.py:60
 #: tickets/models/comment.py:13 tickets/models/ticket/general.py:45
 #: tickets/models/ticket/general.py:279 tickets/serializers/super_ticket.py:14
 #: tickets/serializers/ticket/ticket.py:21
 msgid "State"
 msgstr "状態"
 
-#: accounts/serializers/account/account.py:235
+#: accounts/serializers/account/account.py:237
 msgid "Changed"
 msgstr "編集済み"
 
-#: accounts/serializers/account/account.py:241
+#: accounts/serializers/account/account.py:246
 #: accounts/serializers/automations/base.py:22
 #: assets/models/automations/base.py:19
 #: assets/serializers/automations/base.py:20 ops/models/base.py:17
@@ -612,29 +617,29 @@ msgstr "編集済み"
 msgid "Assets"
 msgstr "資産"
 
-#: accounts/serializers/account/account.py:293
+#: accounts/serializers/account/account.py:298
 msgid "Account already exists"
 msgstr "アカウントはすでに存在しています"
 
-#: accounts/serializers/account/account.py:330
+#: accounts/serializers/account/account.py:348
 #, python-format
 msgid "Asset does not support this secret type: %s"
 msgstr "アセットはアカウント タイプをサポートしていません: %s"
 
-#: accounts/serializers/account/account.py:361
+#: accounts/serializers/account/account.py:379
 msgid "Account has exist"
 msgstr "アカウントはすでに存在しています"
 
-#: accounts/serializers/account/account.py:395
+#: accounts/serializers/account/account.py:413
 #: authentication/serializers/connect_token_secret.py:146
 #: authentication/templates/authentication/_access_key_modal.html:30
 #: perms/models/perm_node.py:21 users/serializers/group.py:33
 msgid "ID"
 msgstr "ID"
 
-#: accounts/serializers/account/account.py:402 acls/models/base.py:98
-#: acls/models/login_acl.py:13 acls/serializers/base.py:55
-#: acls/serializers/login_acl.py:22 assets/models/cmd_filter.py:24
+#: accounts/serializers/account/account.py:420 acls/models/base.py:98
+#: acls/models/login_acl.py:13 acls/serializers/base.py:75
+#: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:24
 #: assets/models/label.py:16 audits/models.py:44 audits/models.py:63
 #: audits/models.py:141 authentication/models/connection_token.py:30
 #: authentication/models/sso_token.py:16
@@ -645,12 +650,12 @@ msgstr "ID"
 #: terminal/models/session/session.py:30 terminal/models/session/sharing.py:32
 #: terminal/notifications.py:96 terminal/notifications.py:144
 #: terminal/serializers/command.py:16 tickets/models/comment.py:21
-#: users/const.py:14 users/models/user.py:911 users/models/user.py:942
+#: users/const.py:14 users/models/user.py:921 users/models/user.py:952
 #: users/serializers/group.py:18
 msgid "User"
 msgstr "ユーザー"
 
-#: accounts/serializers/account/account.py:403
+#: accounts/serializers/account/account.py:421
 #: authentication/templates/authentication/_access_key_modal.html:33
 #: terminal/notifications.py:98 terminal/notifications.py:146
 msgid "Date"
@@ -682,10 +687,14 @@ msgid "Key password"
 msgstr "キーパスワード"
 
 #: accounts/serializers/account/base.py:80
-#: assets/serializers/asset/common.py:305
+#: assets/serializers/asset/common.py:306
 msgid "Spec info"
 msgstr "特別情報"
 
+#: accounts/serializers/account/base.py:81
+msgid "Tip: If no username is required for authentication, fill in `null`"
+msgstr "ヒント: 認証にユーザー名が必要ない場合は、null を入力してください"
+
 #: accounts/serializers/automations/base.py:23
 #: assets/models/asset/common.py:129 assets/models/automations/base.py:18
 #: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21
@@ -722,8 +731,8 @@ msgstr "自動タスク実行履歴"
 
 #: accounts/serializers/automations/change_secret.py:155 audits/const.py:52
 #: audits/models.py:54 audits/signal_handlers/activity_log.py:33
-#: common/const/choices.py:18 ops/const.py:56 ops/serializers/celery.py:39
-#: terminal/const.py:59 terminal/models/session/sharing.py:107
+#: common/const/choices.py:18 ops/const.py:56 ops/serializers/celery.py:40
+#: terminal/const.py:60 terminal/models/session/sharing.py:107
 #: tickets/views/approve.py:114
 msgid "Success"
 msgstr "成功"
@@ -795,14 +804,14 @@ msgstr "優先順位"
 msgid "1-100, the lower the value will be match first"
 msgstr "1-100、低い値は最初に一致します"
 
-#: acls/models/base.py:82 acls/serializers/base.py:75
-#: acls/serializers/login_acl.py:24 assets/models/cmd_filter.py:86
+#: acls/models/base.py:82 acls/serializers/base.py:95
+#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86
 #: authentication/serializers/connect_token_secret.py:80
 msgid "Reviewers"
 msgstr "レビュー担当者"
 
 #: acls/models/base.py:83 authentication/models/access_key.py:17
-#: authentication/models/connection_token.py:49
+#: authentication/models/connection_token.py:50
 #: authentication/templates/authentication/_access_key_modal.html:32
 #: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:27
 #: tickets/const.py:37
@@ -810,7 +819,7 @@ msgid "Active"
 msgstr "アクティブ"
 
 #: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60
-#: ops/serializers/job.py:71 terminal/const.py:67
+#: ops/serializers/job.py:55 terminal/const.py:68
 #: terminal/models/session/session.py:43 terminal/serializers/command.py:18
 #: terminal/templates/terminal/_msg_command_alert.html:12
 #: terminal/templates/terminal/_msg_command_execute_alert.html:10
@@ -852,7 +861,7 @@ msgstr "コマンドフィルタリング"
 msgid "Command confirm"
 msgstr "コマンドの確認"
 
-#: acls/models/login_acl.py:16 acls/serializers/login_acl.py:30
+#: acls/models/login_acl.py:16 acls/serializers/login_acl.py:28
 msgid "Rule"
 msgstr "ルール"
 
@@ -872,11 +881,11 @@ msgstr "ログインasset acl"
 msgid "Login asset confirm"
 msgstr "ログイン資産の確認"
 
-#: acls/serializers/base.py:10 acls/serializers/login_acl.py:17
+#: acls/serializers/base.py:11 acls/serializers/login_acl.py:16
 msgid "With * indicating a match all. "
 msgstr "* はすべて一致することを示します。"
 
-#: acls/serializers/base.py:25
+#: acls/serializers/base.py:26
 msgid ""
 "With * indicating a match all. Such as: 192.168.10.1, 192.168.1.0/24, "
 "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (Domain name "
@@ -886,35 +895,35 @@ msgstr ""
 "10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:db8:1a:1110:::/64 (ドメイン名サ"
 "ポート)"
 
-#: acls/serializers/base.py:40 assets/serializers/asset/host.py:19
+#: acls/serializers/base.py:41 assets/serializers/asset/host.py:19
 msgid "IP/Host"
 msgstr "IP/ホスト"
 
-#: acls/serializers/base.py:60
+#: acls/serializers/base.py:80
 msgid "User (username)"
 msgstr "ユーザー (ユーザー名)"
 
-#: acls/serializers/base.py:64
+#: acls/serializers/base.py:84
 msgid "Asset (name)"
 msgstr "資産(名前)"
 
-#: acls/serializers/base.py:68
+#: acls/serializers/base.py:88
 msgid "Asset (address)"
 msgstr "資産(住所)"
 
-#: acls/serializers/base.py:72
+#: acls/serializers/base.py:92
 msgid "Account (username)"
 msgstr "アカウント (ユーザー名)"
 
-#: acls/serializers/base.py:78 acls/serializers/login_acl.py:28
+#: acls/serializers/base.py:98 acls/serializers/login_acl.py:26
 msgid "Reviewers amount"
 msgstr "承認者数"
 
-#: acls/serializers/base.py:109 tickets/serializers/ticket/ticket.py:76
+#: acls/serializers/base.py:126 tickets/serializers/ticket/ticket.py:76
 msgid "The organization `{}` does not exist"
 msgstr "組織 '{}'は存在しません"
 
-#: acls/serializers/base.py:115
+#: acls/serializers/base.py:132
 msgid "None of the reviewers belong to Organization `{}`"
 msgstr "いずれのレビューアも組織 '{}' に属していません"
 
@@ -1009,7 +1018,7 @@ msgid "Unable to connect to port {port} on {address}"
 msgstr "{port} のポート {address} に接続できません"
 
 #: assets/automations/ping_gateway/manager.py:58
-#: authentication/middleware.py:87 xpack/plugins/cloud/providers/fc.py:48
+#: authentication/middleware.py:92 xpack/plugins/cloud/providers/fc.py:48
 msgid "Authentication failed"
 msgstr "認証に失敗しました"
 
@@ -1102,7 +1111,7 @@ msgstr "ファイアウォール"
 msgid "Other"
 msgstr "その他"
 
-#: assets/const/types.py:218
+#: assets/const/types.py:223
 msgid "All types"
 msgstr "いろんなタイプ"
 
@@ -1140,11 +1149,11 @@ msgstr "SSHパブリックキー"
 #: assets/models/cmd_filter.py:88 assets/models/group.py:23
 #: common/db/models.py:37 ops/models/adhoc.py:27 ops/models/job.py:111
 #: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:38
-#: terminal/models/applet/applet.py:36 terminal/models/applet/applet.py:184
-#: terminal/models/applet/host.py:111 terminal/models/component/endpoint.py:24
+#: terminal/models/applet/applet.py:37 terminal/models/applet/applet.py:218
+#: terminal/models/applet/host.py:139 terminal/models/component/endpoint.py:24
 #: terminal/models/component/endpoint.py:100
 #: terminal/models/session/session.py:47 tickets/models/comment.py:32
-#: tickets/models/ticket/general.py:297 users/models/user.py:756
+#: tickets/models/ticket/general.py:297 users/models/user.py:766
 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:111
 msgid "Comment"
 msgstr "コメント"
@@ -1152,18 +1161,18 @@ msgstr "コメント"
 #: assets/models/_user.py:28 assets/models/automations/base.py:114
 #: assets/models/cmd_filter.py:41 assets/models/group.py:22
 #: common/db/models.py:35 ops/models/base.py:54 ops/models/job.py:191
-#: users/models/user.py:943
+#: users/models/user.py:953
 msgid "Date created"
 msgstr "作成された日付"
 
 #: assets/models/_user.py:29 assets/models/cmd_filter.py:42
-#: common/db/models.py:36 users/models/user.py:777
+#: common/db/models.py:36 users/models/user.py:787
 msgid "Date updated"
 msgstr "更新日"
 
 #: assets/models/_user.py:30 assets/models/cmd_filter.py:44
 #: assets/models/cmd_filter.py:91 assets/models/group.py:21
-#: common/db/models.py:33 users/models/user.py:763
+#: common/db/models.py:33 users/models/user.py:773
 #: users/serializers/group.py:31
 msgid "Created by"
 msgstr "によって作成された"
@@ -1253,7 +1262,7 @@ msgstr "ポート"
 msgid "Address"
 msgstr "アドレス"
 
-#: assets/models/asset/common.py:125 assets/models/platform.py:120
+#: assets/models/asset/common.py:125 assets/models/platform.py:112
 #: authentication/serializers/connect_token_secret.py:107
 #: perms/serializers/user_permission.py:24
 #: xpack/plugins/cloud/serializers/account_attrs.py:196
@@ -1262,7 +1271,7 @@ msgstr "プラットフォーム"
 
 #: assets/models/asset/common.py:127 assets/models/domain.py:21
 #: authentication/serializers/connect_token_secret.py:125
-#: perms/serializers/user_permission.py:28
+#: perms/serializers/user_permission.py:29
 msgid "Domain"
 msgstr "ドメイン"
 
@@ -1270,7 +1279,7 @@ msgstr "ドメイン"
 msgid "Labels"
 msgstr "ラベル"
 
-#: assets/models/asset/common.py:132 assets/serializers/asset/common.py:306
+#: assets/models/asset/common.py:132 assets/serializers/asset/common.py:307
 #: assets/serializers/asset/host.py:11
 msgid "Gathered info"
 msgstr "資産ハードウェア情報の収集"
@@ -1337,7 +1346,7 @@ msgid "Submit selector"
 msgstr "ボタンセレクターを確認する"
 
 #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
-#: assets/serializers/asset/common.py:304 rbac/tree.py:35
+#: assets/serializers/asset/common.py:305 rbac/tree.py:35
 msgid "Accounts"
 msgstr "アカウント"
 
@@ -1355,7 +1364,7 @@ msgstr "アセットの自動化タスク"
 
 #: assets/models/automations/base.py:113 audits/models.py:177
 #: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:184
-#: terminal/models/applet/applet.py:183 terminal/models/applet/host.py:108
+#: terminal/models/applet/applet.py:217 terminal/models/applet/host.py:136
 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18
 #: terminal/serializers/applet_host.py:103 tickets/models/ticket/general.py:283
 #: tickets/serializers/super_ticket.py:13
@@ -1382,7 +1391,7 @@ msgstr "確認済みの日付"
 
 #: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61
 #: perms/serializers/permission.py:32 users/models/group.py:25
-#: users/models/user.py:723
+#: users/models/user.py:733
 msgid "User group"
 msgstr "ユーザーグループ"
 
@@ -1432,7 +1441,7 @@ msgstr "デフォルト"
 msgid "Default asset group"
 msgstr "デフォルトアセットグループ"
 
-#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:928
+#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:938
 msgid "System"
 msgstr "システム"
 
@@ -1448,7 +1457,8 @@ msgstr "値"
 #: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13
 #: assets/serializers/platform.py:93
 #: authentication/serializers/connect_token_secret.py:113
-#: common/serializers/common.py:85 settings/serializers/sms.py:7
+#: common/serializers/common.py:85 perms/serializers/user_permission.py:28
+#: settings/serializers/sms.py:7
 msgid "Label"
 msgstr "ラベル"
 
@@ -1498,102 +1508,102 @@ msgstr "開ける"
 msgid "Setting"
 msgstr "設定"
 
-#: assets/models/platform.py:39 audits/const.py:47 settings/models.py:37
+#: assets/models/platform.py:31 audits/const.py:47 settings/models.py:37
 #: terminal/serializers/applet_host.py:29
 msgid "Enabled"
 msgstr "有効化"
 
-#: assets/models/platform.py:40
+#: assets/models/platform.py:32
 msgid "Ansible config"
 msgstr "Ansible 構成"
 
-#: assets/models/platform.py:42 assets/serializers/platform.py:63
+#: assets/models/platform.py:34 assets/serializers/platform.py:63
 msgid "Ping enabled"
 msgstr "アセット ディスカバリを有効にする"
 
-#: assets/models/platform.py:43 assets/serializers/platform.py:64
+#: assets/models/platform.py:35 assets/serializers/platform.py:64
 msgid "Ping method"
 msgstr "資産検出方法"
 
-#: assets/models/platform.py:44
+#: assets/models/platform.py:36
 msgid "Ping params"
 msgstr "資産検出パラメータ"
 
-#: assets/models/platform.py:46 assets/models/platform.py:70
+#: assets/models/platform.py:38 assets/models/platform.py:62
 #: assets/serializers/platform.py:65
 msgid "Gather facts enabled"
 msgstr "資産情報の収集を有効にする"
 
-#: assets/models/platform.py:48 assets/models/platform.py:72
+#: assets/models/platform.py:40 assets/models/platform.py:64
 #: assets/serializers/platform.py:66
 msgid "Gather facts method"
 msgstr "情報収集の方法"
 
-#: assets/models/platform.py:50 assets/models/platform.py:74
+#: assets/models/platform.py:42 assets/models/platform.py:66
 msgid "Gather facts params"
 msgstr "情報収集パラメータ"
 
-#: assets/models/platform.py:52 assets/serializers/platform.py:69
+#: assets/models/platform.py:44 assets/serializers/platform.py:69
 msgid "Change secret enabled"
 msgstr "パスワードの変更が有効"
 
-#: assets/models/platform.py:54 assets/serializers/platform.py:70
+#: assets/models/platform.py:46 assets/serializers/platform.py:70
 msgid "Change secret method"
 msgstr "パスワード変更モード"
 
-#: assets/models/platform.py:56
+#: assets/models/platform.py:48
 msgid "Change secret params"
 msgstr "パスワード変更パラメータ"
 
-#: assets/models/platform.py:58 assets/serializers/platform.py:71
+#: assets/models/platform.py:50 assets/serializers/platform.py:71
 msgid "Push account enabled"
 msgstr "アカウントのプッシュを有効にする"
 
-#: assets/models/platform.py:60 assets/serializers/platform.py:72
+#: assets/models/platform.py:52 assets/serializers/platform.py:72
 msgid "Push account method"
 msgstr "アカウントプッシュ方式"
 
-#: assets/models/platform.py:62
+#: assets/models/platform.py:54
 msgid "Push account params"
 msgstr "アカウントプッシュパラメータ"
 
-#: assets/models/platform.py:64 assets/serializers/platform.py:67
+#: assets/models/platform.py:56 assets/serializers/platform.py:67
 msgid "Verify account enabled"
 msgstr "アカウントの確認をオンにする"
 
-#: assets/models/platform.py:66 assets/serializers/platform.py:68
+#: assets/models/platform.py:58 assets/serializers/platform.py:68
 msgid "Verify account method"
 msgstr "アカウント認証方法"
 
-#: assets/models/platform.py:68
+#: assets/models/platform.py:60
 msgid "Verify account params"
 msgstr "アカウント認証パラメータ"
 
-#: assets/models/platform.py:92 tickets/models/ticket/general.py:300
+#: assets/models/platform.py:84 tickets/models/ticket/general.py:300
 msgid "Meta"
 msgstr "メタ"
 
-#: assets/models/platform.py:93
+#: assets/models/platform.py:85
 msgid "Internal"
 msgstr "ビルトイン"
 
-#: assets/models/platform.py:97 assets/serializers/platform.py:108
+#: assets/models/platform.py:89 assets/serializers/platform.py:108
 msgid "Charset"
 msgstr "シャーセット"
 
-#: assets/models/platform.py:99 assets/serializers/platform.py:135
+#: assets/models/platform.py:91 assets/serializers/platform.py:136
 msgid "Domain enabled"
 msgstr "ドメインを有効にする"
 
-#: assets/models/platform.py:101 assets/serializers/platform.py:134
+#: assets/models/platform.py:93 assets/serializers/platform.py:135
 msgid "Su enabled"
 msgstr "アカウントの切り替えを有効にする"
 
-#: assets/models/platform.py:102 assets/serializers/platform.py:114
+#: assets/models/platform.py:94 assets/serializers/platform.py:114
 msgid "Su method"
 msgstr "アカウントの切り替え方法"
 
-#: assets/models/platform.py:103 assets/serializers/platform.py:117
+#: assets/models/platform.py:95 assets/serializers/platform.py:117
 msgid "Custom fields"
 msgstr "カスタムフィールド"
 
@@ -1623,23 +1633,29 @@ msgid "Node path"
 msgstr "ノードパスです"
 
 #: assets/serializers/asset/common.py:144
-#: assets/serializers/asset/common.py:307
+#: assets/serializers/asset/common.py:308
 msgid "Auto info"
 msgstr "自動情報"
 
-#: assets/serializers/asset/common.py:226
+#: assets/serializers/asset/common.py:227
 msgid "Platform not exist"
 msgstr "プラットフォームが存在しません"
 
-#: assets/serializers/asset/common.py:262
+#: assets/serializers/asset/common.py:263
 msgid "port out of range (1-65535)"
 msgstr "ポート番号が範囲外です (1-65535)"
 
-#: assets/serializers/asset/common.py:269
+#: assets/serializers/asset/common.py:270
 msgid "Protocol is required: {}"
 msgstr "プロトコルが必要です: {}"
 
-#: assets/serializers/asset/database.py:25 common/serializers/fields.py:103
+#: assets/serializers/asset/database.py:13
+#, fuzzy
+#| msgid "Default storage"
+msgid "Default database"
+msgstr "デフォルトのストレージ"
+
+#: assets/serializers/asset/database.py:28 common/serializers/fields.py:103
 #: tickets/serializers/ticket/common.py:58
 #: xpack/plugins/cloud/serializers/account_attrs.py:56
 #: xpack/plugins/cloud/serializers/account_attrs.py:79
@@ -1757,15 +1773,15 @@ msgstr ""
 msgid "Automation"
 msgstr "オートメーション"
 
-#: assets/serializers/platform.py:136
+#: assets/serializers/platform.py:137
 msgid "Default Domain"
 msgstr "デフォルト ドメイン"
 
-#: assets/serializers/platform.py:145
+#: assets/serializers/platform.py:146
 msgid "type is required"
 msgstr "タイプ このフィールドは必須です."
 
-#: assets/serializers/platform.py:182
+#: assets/serializers/platform.py:183
 msgid "Protocols is required"
 msgstr "同意が必要です"
 
@@ -1921,7 +1937,7 @@ msgstr "セッションログ"
 msgid "Login log"
 msgstr "ログインログ"
 
-#: audits/const.py:42 terminal/models/applet/host.py:112
+#: audits/const.py:42 terminal/models/applet/host.py:140
 #: terminal/models/component/task.py:24
 msgid "Task"
 msgstr "タスク"
@@ -2017,7 +2033,7 @@ msgstr "ユーザーエージェント"
 
 #: audits/models.py:169 audits/serializers.py:47
 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
-#: users/forms/profile.py:65 users/models/user.py:740
+#: users/forms/profile.py:65 users/models/user.py:750
 #: users/serializers/profile.py:126
 msgid "MFA"
 msgstr "MFA"
@@ -2071,22 +2087,24 @@ msgid "Auth Token"
 msgstr "認証トークン"
 
 #: audits/signal_handlers/login_log.py:31 authentication/notifications.py:73
-#: authentication/views/login.py:74 authentication/views/wecom.py:177
+#: authentication/views/login.py:74 authentication/views/wecom.py:159
 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10
-#: users/models/user.py:778
+#: users/models/user.py:680 users/models/user.py:788
 msgid "WeCom"
 msgstr "企業微信"
 
-#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:144
+#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:123
 #: authentication/views/login.py:86 notifications/backends/__init__.py:14
 #: settings/serializers/auth/feishu.py:10
-#: settings/serializers/auth/feishu.py:13 users/models/user.py:780
+#: settings/serializers/auth/feishu.py:13 users/models/user.py:682
+#: users/models/user.py:790
 msgid "FeiShu"
 msgstr "本を飛ばす"
 
-#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:179
+#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:160
 #: authentication/views/login.py:80 notifications/backends/__init__.py:12
-#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:779
+#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:681
+#: users/models/user.py:789
 msgid "DingTalk"
 msgstr "DingTalk"
 
@@ -2103,19 +2121,19 @@ msgstr "監査セッション タスク ログのクリーンアップ"
 msgid "This action require verify your MFA"
 msgstr "この操作には、MFAを検証する必要があります"
 
-#: authentication/api/connection_token.py:296
+#: authentication/api/connection_token.py:303
 msgid "Account not found"
 msgstr "アカウントが見つかりません"
 
-#: authentication/api/connection_token.py:299
+#: authentication/api/connection_token.py:306
 msgid "Permission expired"
 msgstr "承認の有効期限が切れています"
 
-#: authentication/api/connection_token.py:311
+#: authentication/api/connection_token.py:318
 msgid "ACL action is reject"
 msgstr "ACL アクションは拒否です"
 
-#: authentication/api/connection_token.py:315
+#: authentication/api/connection_token.py:322
 msgid "ACL action is review"
 msgstr "ACL アクションはレビューです"
 
@@ -2123,7 +2141,7 @@ msgstr "ACL アクションはレビューです"
 msgid "Current user not support mfa type: {}"
 msgstr "現在のユーザーはmfaタイプをサポートしていません: {}"
 
-#: authentication/api/password.py:31 terminal/api/session/session.py:247
+#: authentication/api/password.py:31 terminal/api/session/session.py:249
 #: users/views/profile/reset.py:44
 msgid "User does not exist: {}"
 msgstr "ユーザーが存在しない: {}"
@@ -2347,21 +2365,21 @@ msgstr "電話が設定されていない"
 msgid "SSO auth closed"
 msgstr "SSO authは閉鎖されました"
 
-#: authentication/errors/mfa.py:18 authentication/views/wecom.py:79
+#: authentication/errors/mfa.py:18 authentication/views/wecom.py:61
 msgid "WeCom is already bound"
 msgstr "企業の微信はすでにバインドされています"
 
-#: authentication/errors/mfa.py:23 authentication/views/wecom.py:236
-#: authentication/views/wecom.py:290
+#: authentication/errors/mfa.py:23 authentication/views/wecom.py:202
+#: authentication/views/wecom.py:244
 msgid "WeCom is not bound"
 msgstr "企業の微信をバインドしていません"
 
-#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:242
-#: authentication/views/dingtalk.py:296
+#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:210
+#: authentication/views/dingtalk.py:252
 msgid "DingTalk is not bound"
 msgstr "DingTalkはバインドされていません"
 
-#: authentication/errors/mfa.py:33 authentication/views/feishu.py:203
+#: authentication/errors/mfa.py:33 authentication/views/feishu.py:167
 msgid "FeiShu is not bound"
 msgstr "本を飛ばすは拘束されていません"
 
@@ -2369,15 +2387,15 @@ msgstr "本を飛ばすは拘束されていません"
 msgid "Your password is invalid"
 msgstr "パスワードが無効です"
 
-#: authentication/errors/redirect.py:85 authentication/mixins.py:307
+#: authentication/errors/redirect.py:85 authentication/mixins.py:316
 msgid "Your password is too simple, please change it for security"
 msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください"
 
-#: authentication/errors/redirect.py:93 authentication/mixins.py:314
+#: authentication/errors/redirect.py:93 authentication/mixins.py:323
 msgid "You should to change your password before login"
 msgstr "ログインする前にパスワードを変更する必要があります"
 
-#: authentication/errors/redirect.py:101 authentication/mixins.py:321
+#: authentication/errors/redirect.py:101 authentication/mixins.py:330
 msgid "Your password has expired, please reset before logging in"
 msgstr ""
 "パスワードの有効期限が切れました。ログインする前にリセットしてください。"
@@ -2476,15 +2494,23 @@ msgstr "電話番号を設定して有効にする"
 msgid "Clear phone number to disable"
 msgstr "無効にする電話番号をクリアする"
 
-#: authentication/middleware.py:88 settings/utils/ldap.py:652
+#: authentication/middleware.py:93 settings/utils/ldap.py:652
 msgid "Authentication failed (before login check failed): {}"
 msgstr "認証に失敗しました (ログインチェックが失敗する前): {}"
 
-#: authentication/mixins.py:257
+#: authentication/mixins.py:91
+msgid ""
+"The administrator has enabled 'Only allow login from user source'. \n"
+"            The current user source is {}. Please contact the administrator."
+msgstr ""
+"管理者は「ユーザーソースからのみログインを許可」をオンにしており、現在のユー"
+"ザーソースは {} です。管理者に連絡してください。"
+
+#: authentication/mixins.py:266
 msgid "The MFA type ({}) is not enabled"
 msgstr "MFAタイプ ({}) が有効になっていない"
 
-#: authentication/mixins.py:297
+#: authentication/mixins.py:306
 msgid "Please change your password"
 msgstr "パスワードを変更してください"
 
@@ -2498,7 +2524,7 @@ msgid "Input username"
 msgstr "カスタム ユーザー名"
 
 #: authentication/models/connection_token.py:38
-#: authentication/serializers/connection_token.py:17
+#: authentication/serializers/connection_token.py:20
 msgid "Input secret"
 msgstr "カスタムパスワード"
 
@@ -2516,34 +2542,40 @@ msgid "Asset display"
 msgstr "アセット名"
 
 #: authentication/models/connection_token.py:43
+#, fuzzy
+#| msgid "Disable"
+msgid "Reusable"
+msgstr "無効化"
+
+#: authentication/models/connection_token.py:44
 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:74
 #: tickets/models/ticket/apply_application.py:31
-#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:761
+#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:771
 msgid "Date expired"
 msgstr "期限切れの日付"
 
-#: authentication/models/connection_token.py:47
+#: authentication/models/connection_token.py:48
 #: perms/models/asset_permission.py:77
 msgid "From ticket"
 msgstr "チケットから"
 
-#: authentication/models/connection_token.py:53
+#: authentication/models/connection_token.py:54
 msgid "Connection token"
 msgstr "接続トークン"
 
-#: authentication/models/connection_token.py:55
+#: authentication/models/connection_token.py:56
 msgid "Can view connection token secret"
 msgstr "接続トークンの秘密を表示できます"
 
-#: authentication/models/connection_token.py:102
+#: authentication/models/connection_token.py:103
 msgid "Connection token inactive"
 msgstr "接続トークンがアクティブ化されていません"
 
-#: authentication/models/connection_token.py:105
+#: authentication/models/connection_token.py:106
 msgid "Connection token expired at: {}"
 msgstr "接続トークンの有効期限: {}"
 
-#: authentication/models/connection_token.py:108
+#: authentication/models/connection_token.py:109
 msgid "No user or invalid user"
 msgstr "ユーザーなしまたは期限切れのユーザー"
 
@@ -2595,15 +2627,15 @@ msgstr "コンポーネント"
 msgid "Expired now"
 msgstr "すぐに期限切れ"
 
-#: authentication/serializers/connection_token.py:15
+#: authentication/serializers/connection_token.py:18
 msgid "Expired time"
 msgstr "期限切れ時間"
 
-#: authentication/serializers/connection_token.py:19
+#: authentication/serializers/connection_token.py:22
 msgid "Ticket info"
 msgstr "作業指示情報"
 
-#: authentication/serializers/connection_token.py:20
+#: authentication/serializers/connection_token.py:23
 #: perms/models/asset_permission.py:71 perms/serializers/permission.py:36
 #: perms/serializers/permission.py:69
 #: tickets/models/ticket/apply_application.py:28
@@ -2611,17 +2643,21 @@ msgstr "作業指示情報"
 msgid "Actions"
 msgstr "アクション"
 
-#: authentication/serializers/connection_token.py:41
+#: authentication/serializers/connection_token.py:44
 #: perms/serializers/permission.py:38 perms/serializers/permission.py:70
 #: users/serializers/user.py:97 users/serializers/user.py:172
 msgid "Is expired"
 msgstr "期限切れです"
 
+#: authentication/serializers/connection_token.py:79
+msgid "Reusable connection token is not allowed, global setting not enabled"
+msgstr ""
+
 #: authentication/serializers/password_mfa.py:16
 #: authentication/serializers/password_mfa.py:24
 #: notifications/backends/__init__.py:10 settings/serializers/email.py:19
 #: settings/serializers/email.py:50 users/forms/profile.py:102
-#: users/forms/profile.py:106 users/models/user.py:719
+#: users/forms/profile.py:106 users/models/user.py:729
 #: users/templates/users/forgot_password.html:116
 #: users/views/profile/reset.py:73
 msgid "Email"
@@ -2711,7 +2747,7 @@ msgstr "コードエラー"
 #: authentication/templates/authentication/_msg_reset_password_code.html:9
 #: authentication/templates/authentication/_msg_rest_password_success.html:2
 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
-#: jumpserver/conf.py:417
+#: jumpserver/conf.py:419
 #: perms/templates/perms/_msg_item_permissions_expire.html:3
 #: perms/templates/perms/_msg_permed_items_expire.html:3
 #: tickets/templates/tickets/approve_check_password.html:33
@@ -2863,76 +2899,77 @@ msgstr "コピー成功"
 msgid "LAN"
 msgstr "ローカルエリアネットワーク"
 
-#: authentication/views/dingtalk.py:41
+#: authentication/views/base.py:64
+#: perms/templates/perms/_msg_permed_items_expire.html:21
+msgid "If you have any question, please contact the administrator"
+msgstr "質問があったら、管理者に連絡して下さい"
+
+#: authentication/views/dingtalk.py:42
 msgid "DingTalk Error, Please contact your system administrator"
 msgstr "DingTalkエラー、システム管理者に連絡してください"
 
-#: authentication/views/dingtalk.py:44
+#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:209
 msgid "DingTalk Error"
 msgstr "DingTalkエラー"
 
-#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:51
-#: authentication/views/wecom.py:55
+#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:51
+#: authentication/views/wecom.py:57
 msgid ""
 "The system configuration is incorrect. Please contact your administrator"
 msgstr "システム設定が正しくありません。管理者に連絡してください"
 
-#: authentication/views/dingtalk.py:80
+#: authentication/views/dingtalk.py:61
 msgid "DingTalk is already bound"
 msgstr "DingTalkはすでにバインドされています"
 
-#: authentication/views/dingtalk.py:148 authentication/views/wecom.py:147
+#: authentication/views/dingtalk.py:129 authentication/views/wecom.py:129
 msgid "Invalid user_id"
 msgstr "無効なuser_id"
 
-#: authentication/views/dingtalk.py:164
+#: authentication/views/dingtalk.py:145
 msgid "DingTalk query user failed"
 msgstr "DingTalkクエリユーザーが失敗しました"
 
-#: authentication/views/dingtalk.py:173
+#: authentication/views/dingtalk.py:154
 msgid "The DingTalk is already bound to another user"
 msgstr "DingTalkはすでに別のユーザーにバインドされています"
 
-#: authentication/views/dingtalk.py:180
+#: authentication/views/dingtalk.py:161
 msgid "Binding DingTalk successfully"
 msgstr "DingTalkのバインドに成功"
 
-#: authentication/views/dingtalk.py:236 authentication/views/dingtalk.py:290
+#: authentication/views/dingtalk.py:211 authentication/views/dingtalk.py:246
 msgid "Failed to get user from DingTalk"
 msgstr "DingTalkからユーザーを取得できませんでした"
 
-#: authentication/views/dingtalk.py:243 authentication/views/dingtalk.py:297
+#: authentication/views/dingtalk.py:253
 msgid "Please login with a password and then bind the DingTalk"
 msgstr "パスワードでログインし、DingTalkをバインドしてください"
 
-#: authentication/views/feishu.py:39
+#: authentication/views/feishu.py:39 authentication/views/feishu.py:166
 msgid "FeiShu Error"
 msgstr "FeiShuエラー"
 
-#: authentication/views/feishu.py:87
+#: authentication/views/feishu.py:67
 msgid "FeiShu is already bound"
 msgstr "FeiShuはすでにバインドされています"
 
-#: authentication/views/feishu.py:129
+#: authentication/views/feishu.py:108
 msgid "FeiShu query user failed"
 msgstr "FeiShuクエリユーザーが失敗しました"
 
-#: authentication/views/feishu.py:138
+#: authentication/views/feishu.py:117
 msgid "The FeiShu is already bound to another user"
 msgstr "FeiShuはすでに別のユーザーにバインドされています"
 
-#: authentication/views/feishu.py:145
+#: authentication/views/feishu.py:124
 msgid "Binding FeiShu successfully"
 msgstr "本を飛ばすのバインドに成功"
 
-#: authentication/views/feishu.py:197
+#: authentication/views/feishu.py:168
 msgid "Failed to get user from FeiShu"
 msgstr "本を飛ばすからユーザーを取得できませんでした"
 
-#: authentication/views/feishu.py:204
-msgid "Please login with a password and then bind the FeiShu"
-msgstr "パスワードでログインしてから本を飛ばすをバインドしてください"
-
 #: authentication/views/login.py:182
 msgid "Redirecting"
 msgstr "リダイレクト"
@@ -2969,31 +3006,31 @@ msgstr "ログアウト成功"
 msgid "Logout success, return login page"
 msgstr "ログアウト成功、ログインページを返す"
 
-#: authentication/views/wecom.py:40
+#: authentication/views/wecom.py:42
 msgid "WeCom Error, Please contact your system administrator"
 msgstr "企業微信エラー、システム管理者に連絡してください"
 
-#: authentication/views/wecom.py:43
+#: authentication/views/wecom.py:45 authentication/views/wecom.py:201
 msgid "WeCom Error"
 msgstr "企業微信エラー"
 
-#: authentication/views/wecom.py:162
+#: authentication/views/wecom.py:144
 msgid "WeCom query user failed"
 msgstr "企業微信ユーザーの問合せに失敗しました"
 
-#: authentication/views/wecom.py:171
+#: authentication/views/wecom.py:153
 msgid "The WeCom is already bound to another user"
 msgstr "この企業の微信はすでに他のユーザーをバインドしている。"
 
-#: authentication/views/wecom.py:178
+#: authentication/views/wecom.py:160
 msgid "Binding WeCom successfully"
 msgstr "企業の微信のバインドに成功"
 
-#: authentication/views/wecom.py:230 authentication/views/wecom.py:284
+#: authentication/views/wecom.py:203 authentication/views/wecom.py:238
 msgid "Failed to get user from WeCom"
 msgstr "企業の微信からユーザーを取得できませんでした"
 
-#: authentication/views/wecom.py:237 authentication/views/wecom.py:291
+#: authentication/views/wecom.py:245
 msgid "Please login with a password and then bind the WeCom"
 msgstr "パスワードでログインしてからWeComをバインドしてください"
 
@@ -3013,7 +3050,7 @@ msgstr "タイミングトリガー"
 msgid "Ready"
 msgstr "の準備を"
 
-#: common/const/choices.py:16 terminal/const.py:58 tickets/const.py:29
+#: common/const/choices.py:16 terminal/const.py:59 tickets/const.py:29
 #: tickets/const.py:39
 msgid "Pending"
 msgstr "未定"
@@ -3076,7 +3113,7 @@ msgstr "は破棄されます"
 msgid "discard time"
 msgstr "時間を捨てる"
 
-#: common/db/models.py:34 users/models/user.py:764
+#: common/db/models.py:34 users/models/user.py:774
 msgid "Updated by"
 msgstr "によって更新"
 
@@ -3104,6 +3141,14 @@ msgstr "解析ファイルエラー: {}"
 msgid "Invalid excel file"
 msgstr "無効 excel 書類"
 
+#: common/drf/renders/base.py:209
+msgid ""
+"{} - The encryption password has not been set - please go to personal "
+"information -> file encryption password to set the encryption password"
+msgstr ""
+"{} - 暗号化パスワードが設定されていません-個人情報->ファイル暗号化パスワード"
+"に暗号化パスワードを設定してください"
+
 #: common/exceptions.py:15
 #, python-format
 msgid "%s object does not exist."
@@ -3145,7 +3190,7 @@ msgstr "サポートされていません Elasticsearch8"
 msgid "Network error, please contact system administrator"
 msgstr "ネットワークエラー、システム管理者に連絡してください"
 
-#: common/sdk/im/wecom/__init__.py:15
+#: common/sdk/im/wecom/__init__.py:16
 msgid "WeCom error, please contact system administrator"
 msgstr "企業微信エラー、システム管理者に連絡してください"
 
@@ -3274,11 +3319,11 @@ msgstr "検索のエクスポート: %s"
 msgid "User %s view/export secret"
 msgstr "ユーザー %s がパスワードを閲覧/導き出しました"
 
-#: jumpserver/conf.py:416
+#: jumpserver/conf.py:418
 msgid "Create account successfully"
 msgstr "アカウントを正常に作成"
 
-#: jumpserver/conf.py:418
+#: jumpserver/conf.py:420
 msgid "Your account has been created successfully"
 msgstr "アカウントが正常に作成されました"
 
@@ -3346,15 +3391,15 @@ msgstr "システムメッセージ"
 msgid "Publish the station message"
 msgstr "投稿サイトニュース"
 
-#: ops/ansible/inventory.py:77
+#: ops/ansible/inventory.py:82
 msgid "No account available"
 msgstr "利用可能なアカウントがありません"
 
-#: ops/ansible/inventory.py:236
+#: ops/ansible/inventory.py:247
 msgid "Ansible disabled"
 msgstr "Ansible 無効"
 
-#: ops/ansible/inventory.py:252
+#: ops/ansible/inventory.py:263
 msgid "Skip hosts below:"
 msgstr "次のホストをスキップします: "
 
@@ -3620,15 +3665,15 @@ msgstr "{max_threshold} を超えるCPUロード: => {value}"
 msgid "Run after save"
 msgstr "保存後に実行"
 
-#: ops/serializers/job.py:70
+#: ops/serializers/job.py:54
 msgid "Job type"
 msgstr "タスクの種類"
 
-#: ops/serializers/job.py:73 terminal/serializers/session.py:49
+#: ops/serializers/job.py:57 terminal/serializers/session.py:49
 msgid "Is finished"
 msgstr "終了しました"
 
-#: ops/serializers/job.py:74
+#: ops/serializers/job.py:58
 msgid "Time cost"
 msgstr "時を過ごす"
 
@@ -3660,6 +3705,10 @@ msgstr "例外ジョブのクリーンアップ"
 msgid "Task log"
 msgstr "タスクログ"
 
+#: ops/templates/ops/celery_task_log.html:71
+msgid "Task name"
+msgstr "タスク名"
+
 #: ops/variables.py:24
 msgid "The current user`s username of JumpServer"
 msgstr "JumpServerの現在のユーザーのユーザー名"
@@ -3846,10 +3895,6 @@ msgstr ""
 "        次の %(item_type)s は %(count)s 日以内に期限切れになります\n"
 "    "
 
-#: perms/templates/perms/_msg_permed_items_expire.html:21
-msgid "If you have any question, please contact the administrator"
-msgstr "質問があったら、管理者に連絡して下さい"
-
 #: rbac/api/role.py:35
 msgid "Internal role, can't be destroy"
 msgstr "内部の役割は、破壊することはできません"
@@ -3928,7 +3973,7 @@ msgid "Scope"
 msgstr "スコープ"
 
 #: rbac/models/role.py:46 rbac/models/rolebinding.py:52
-#: users/models/user.py:727
+#: users/models/user.py:737
 msgid "Role"
 msgstr "ロール"
 
@@ -3944,22 +3989,22 @@ msgstr "組織の役割"
 msgid "Role binding"
 msgstr "ロールバインディング"
 
-#: rbac/models/rolebinding.py:145
+#: rbac/models/rolebinding.py:153
 msgid "All organizations"
 msgstr "全ての組織"
 
-#: rbac/models/rolebinding.py:174
+#: rbac/models/rolebinding.py:182
 msgid ""
 "User last role in org, can not be delete, you can remove user from org "
 "instead"
 msgstr ""
 "ユーザーの最後のロールは削除できません。ユーザーを組織から削除できます。"
 
-#: rbac/models/rolebinding.py:181
+#: rbac/models/rolebinding.py:189
 msgid "Organization role binding"
 msgstr "組織の役割バインディング"
 
-#: rbac/models/rolebinding.py:196
+#: rbac/models/rolebinding.py:204
 msgid "System role binding"
 msgstr "システムロールバインディング"
 
@@ -4035,8 +4080,8 @@ msgstr "タスクセンター"
 msgid "My assets"
 msgstr "私の資産"
 
-#: rbac/tree.py:56 terminal/models/applet/applet.py:43
-#: terminal/models/applet/applet.py:180 terminal/models/applet/host.py:28
+#: rbac/tree.py:56 terminal/models/applet/applet.py:44
+#: terminal/models/applet/applet.py:214 terminal/models/applet/host.py:28
 #: terminal/serializers/applet.py:15
 msgid "Applet"
 msgstr "リモートアプリケーション"
@@ -4907,24 +4952,42 @@ msgid "Only single device login"
 msgstr "単一デバイスログインのみ"
 
 #: settings/serializers/security.py:97
-msgid "Next device login, pre login will be logout"
-msgstr "次のデバイスログイン、事前ログインはログアウトになります"
+msgid ""
+"After the user logs in on the new device, other logged-in devices will "
+"automatically log out"
+msgstr ""
+"ユーザーが新しいデバイスにログインすると、ログインしている他のデバイスは自動"
+"的にログアウトします。"
 
 #: settings/serializers/security.py:100
 msgid "Only exist user login"
 msgstr "ユーザーログインのみ存在"
 
 #: settings/serializers/security.py:101
-msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet"
-msgstr "Enableの場合、ユーザーがまだ存在しない場合、CAS、OIDC authは失敗します"
+msgid ""
+"If enabled, non-existent users will not be allowed to log in; if disabled, "
+"users of other authentication methods except local authentication methods "
+"are allowed to log in and automatically create users (if the user does not "
+"exist)"
+msgstr ""
+"有効にすると、存在しないユーザーはログインできなくなります。無効にすると、"
+"ローカル認証方法を除く他の認証方法のユーザーはログインでき、ユーザーが自動的"
+"に作成されます (ユーザーが存在しない場合)。"
 
 #: settings/serializers/security.py:104
 msgid "Only from source login"
 msgstr "ソースログインからのみ"
 
 #: settings/serializers/security.py:105
-msgid "Only log in from the user source property"
-msgstr "ユーザーソースのプロパティからのみログイン"
+msgid ""
+"If it is enabled, the user will only authenticate to the source when logging "
+"in; if it is disabled, the user will authenticate all the enabled "
+"authentication methods in a certain order when logging in, and as long as "
+"one of the authentication methods is successful, they can log in directly"
+msgstr ""
+"これが有効な場合、ユーザーはログイン時にソースに対してのみ認証されます。無効"
+"な場合、ユーザーはログイン時に、いずれかの認証方法が成功する限り、有効なすべ"
+"ての認証方法を特定の順序で認証します。 、直接ログインできます"
 
 #: settings/serializers/security.py:109
 msgid "MFA verify TTL"
@@ -5476,15 +5539,15 @@ msgstr "テスト失敗: アカウントが無効"
 msgid "Have online sessions"
 msgstr "オンラインセッションを持つ"
 
-#: terminal/api/session/session.py:239
+#: terminal/api/session/session.py:241
 msgid "Session does not exist: {}"
 msgstr "セッションが存在しません: {}"
 
-#: terminal/api/session/session.py:242
+#: terminal/api/session/session.py:244
 msgid "Session is finished or the protocol not supported"
 msgstr "セッションが終了したか、プロトコルがサポートされていません"
 
-#: terminal/api/session/session.py:255
+#: terminal/api/session/session.py:257
 msgid "User does not have permission"
 msgstr "ユーザーに権限がありません"
 
@@ -5537,7 +5600,7 @@ msgstr "クリティカル"
 msgid "High"
 msgstr "高い"
 
-#: terminal/const.py:32 terminal/const.py:65
+#: terminal/const.py:32 terminal/const.py:66
 #: users/templates/users/reset_password.html:50
 msgid "Normal"
 msgstr "正常"
@@ -5546,19 +5609,19 @@ msgstr "正常"
 msgid "Offline"
 msgstr "オフライン"
 
-#: terminal/const.py:61
+#: terminal/const.py:62
 msgid "Mismatch"
 msgstr "一致しない"
 
-#: terminal/const.py:66
+#: terminal/const.py:67
 msgid "Tunnel"
 msgstr ""
 
-#: terminal/const.py:71
+#: terminal/const.py:72
 msgid "Read Only"
 msgstr "読み取り専用"
 
-#: terminal/const.py:72
+#: terminal/const.py:73
 msgid "Writable"
 msgstr "書き込み可能"
 
@@ -5575,31 +5638,37 @@ msgid "Author"
 msgstr "著者"
 
 #: terminal/models/applet/applet.py:35
+#, fuzzy
+#| msgid "Can push account"
+msgid "Can concurrent"
+msgstr "アカウントをプッシュできます"
+
+#: terminal/models/applet/applet.py:36
 msgid "Tags"
 msgstr "ラベル"
 
-#: terminal/models/applet/applet.py:39 terminal/serializers/storage.py:157
+#: terminal/models/applet/applet.py:40 terminal/serializers/storage.py:157
 msgid "Hosts"
 msgstr "ホスト"
 
-#: terminal/models/applet/applet.py:84
+#: terminal/models/applet/applet.py:85
 msgid "Applet pkg not valid, Missing file {}"
 msgstr "無効なアプレット パッケージ、ファイル {} がありません"
 
-#: terminal/models/applet/applet.py:103
+#: terminal/models/applet/applet.py:104
 msgid "Load platform.yml failed: {}"
 msgstr ""
 
-#: terminal/models/applet/applet.py:106
+#: terminal/models/applet/applet.py:107
 msgid "Only support custom platform"
 msgstr ""
 
-#: terminal/models/applet/applet.py:111
+#: terminal/models/applet/applet.py:112
 msgid "Missing type in platform.yml"
 msgstr ""
 
-#: terminal/models/applet/applet.py:182 terminal/models/applet/host.py:34
-#: terminal/models/applet/host.py:106
+#: terminal/models/applet/applet.py:216 terminal/models/applet/host.py:34
+#: terminal/models/applet/host.py:134
 msgid "Hosting"
 msgstr "ホスト マシン"
 
@@ -5619,7 +5688,7 @@ msgstr ""
 msgid "Date synced"
 msgstr "同期日"
 
-#: terminal/models/applet/host.py:107
+#: terminal/models/applet/host.py:135
 msgid "Initial"
 msgstr "初期化"
 
@@ -6058,7 +6127,7 @@ msgstr "端末の状態を定期的にクリーンアップする"
 
 #: terminal/tasks.py:37
 msgid "Clean orphan session"
-msgstr "孤立したセッションをクリアする"
+msgstr "オフライン セッションをクリアする"
 
 #: terminal/tasks.py:56
 msgid "Upload session replay to external storage"
@@ -6072,6 +6141,12 @@ msgstr "アプリケーション マシンの展開を実行する"
 msgid "Install applet"
 msgstr "アプリをインストールする"
 
+#: terminal/tasks.py:104
+#, fuzzy
+#| msgid "Gather assets accounts"
+msgid "Generate applet host accounts"
+msgstr "資産の口座番号を収集する"
+
 #: terminal/templates/terminal/_msg_command_alert.html:10
 msgid "view"
 msgstr "表示"
@@ -6434,7 +6509,7 @@ msgstr "無効な承認アクション"
 msgid "This user is not authorized to approve this ticket"
 msgstr "このユーザーはこの作業指示を承認する権限がありません"
 
-#: users/api/user.py:182
+#: users/api/user.py:185
 msgid "Could not reset self otp, use profile reset instead"
 msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用"
 
@@ -6545,7 +6620,7 @@ msgstr "公開鍵は古いものと同じであってはなりません。"
 msgid "Not a valid ssh public key"
 msgstr "有効なssh公開鍵ではありません"
 
-#: users/forms/profile.py:170 users/models/user.py:750
+#: users/forms/profile.py:170 users/models/user.py:760
 msgid "Public key"
 msgstr "公開キー"
 
@@ -6553,68 +6628,68 @@ msgstr "公開キー"
 msgid "Force enable"
 msgstr "強制有効"
 
-#: users/models/user.py:729 users/serializers/user.py:171
+#: users/models/user.py:739 users/serializers/user.py:171
 msgid "Is service account"
 msgstr "サービスアカウントです"
 
-#: users/models/user.py:731
+#: users/models/user.py:741
 msgid "Avatar"
 msgstr "アバター"
 
-#: users/models/user.py:734
+#: users/models/user.py:744
 msgid "Wechat"
 msgstr "微信"
 
-#: users/models/user.py:737 users/serializers/user.py:109
+#: users/models/user.py:747 users/serializers/user.py:109
 msgid "Phone"
 msgstr "電話"
 
-#: users/models/user.py:743
+#: users/models/user.py:753
 msgid "OTP secret key"
 msgstr "OTP 秘密"
 
-#: users/models/user.py:747
+#: users/models/user.py:757
 msgid "Private key"
 msgstr "ssh秘密鍵"
 
-#: users/models/user.py:753
+#: users/models/user.py:763
 msgid "Secret key"
 msgstr "秘密キー"
 
-#: users/models/user.py:758 users/serializers/profile.py:149
+#: users/models/user.py:768 users/serializers/profile.py:149
 #: users/serializers/user.py:168
 msgid "Is first login"
 msgstr "最初のログインです"
 
-#: users/models/user.py:772
+#: users/models/user.py:782
 msgid "Date password last updated"
 msgstr "最終更新日パスワード"
 
-#: users/models/user.py:775
+#: users/models/user.py:785
 msgid "Need update password"
 msgstr "更新パスワードが必要"
 
-#: users/models/user.py:913
+#: users/models/user.py:923
 msgid "Can invite user"
 msgstr "ユーザーを招待できます"
 
-#: users/models/user.py:914
+#: users/models/user.py:924
 msgid "Can remove user"
 msgstr "ユーザーを削除できます"
 
-#: users/models/user.py:915
+#: users/models/user.py:925
 msgid "Can match user"
 msgstr "ユーザーに一致できます"
 
-#: users/models/user.py:924
+#: users/models/user.py:934
 msgid "Administrator"
 msgstr "管理者"
 
-#: users/models/user.py:927
+#: users/models/user.py:937
 msgid "Administrator is the super user of system"
 msgstr "管理者はシステムのスーパーユーザーです"
 
-#: users/models/user.py:952
+#: users/models/user.py:962
 msgid "User password history"
 msgstr "ユーザーパスワード履歴"
 
@@ -6717,6 +6792,15 @@ msgstr "セキュリティのために、複数のユーザーのみをリスト
 msgid "name not unique"
 msgstr "名前が一意ではない"
 
+#: users/signal_handlers.py:27
+msgid ""
+"The administrator has enabled \"Only allow existing users to log in\", \n"
+"            and the current user is not in the user list. Please contact the "
+"administrator."
+msgstr ""
+"管理者は「既存のユーザーのみログインを許可」をオンにしており、現在のユーザー"
+"はユーザーリストにありません。管理者に連絡してください。"
+
 #: users/tasks.py:21
 msgid "Check password expired"
 msgstr "パスワードの有効期限が切れていることを確認する"
@@ -7559,3 +7643,11 @@ msgstr "究極のエディション"
 #: xpack/plugins/license/models.py:86
 msgid "Community edition"
 msgstr "コミュニティ版"
+
+#, fuzzy
+#~| msgid "Trigger mode"
+#~ msgid "Trigger type"
+#~ msgstr "トリガーモード"
+
+#~ msgid "Please login with a password and then bind the FeiShu"
+#~ msgstr "パスワードでログインしてから本を飛ばすをバインドしてください"
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index 521993edd..ac7231fc1 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:0788b48bc50cffe3e7ff83803ef0edadfc120c9165bfe6bccd1f896d8bf39397
-size 114419
+oid sha256:bd60ca8b6c43b9b5940b14a8ca8073ae26062a5402f663ac39043cbc669199bd
+size 116040
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 4683b46c6..91582121a 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: 2023-04-21 14:05+0800\n"
+"POT-Creation-Date: 2023-05-16 18:32+0800\n"
 "PO-Revision-Date: 2021-05-20 10:54+0800\n"
 "Last-Translator: ibuler \n"
 "Language-Team: JumpServer team\n"
@@ -69,7 +69,7 @@ msgstr "数据库"
 msgid "Collected"
 msgstr "收集"
 
-#: accounts/const/account.py:21 accounts/serializers/account/account.py:25
+#: accounts/const/account.py:21 accounts/serializers/account/account.py:26
 #: settings/serializers/auth/sms.py:75
 msgid "Template"
 msgstr "模板"
@@ -86,7 +86,7 @@ msgstr "更新"
 #: accounts/const/account.py:27
 #: accounts/serializers/automations/change_secret.py:156 audits/const.py:53
 #: audits/signal_handlers/activity_log.py:33 common/const/choices.py:19
-#: ops/const.py:58 terminal/const.py:60 xpack/plugins/cloud/const.py:41
+#: ops/const.py:58 terminal/const.py:61 xpack/plugins/cloud/const.py:41
 msgid "Failed"
 msgstr "失败"
 
@@ -178,14 +178,14 @@ msgstr "创建并推送"
 msgid "Only create"
 msgstr "仅创建"
 
-#: accounts/models/account.py:47
+#: accounts/models/account.py:49
 #: accounts/models/automations/gather_account.py:16
-#: accounts/serializers/account/account.py:199
-#: accounts/serializers/account/account.py:232
+#: accounts/serializers/account/account.py:201
+#: accounts/serializers/account/account.py:234
 #: accounts/serializers/account/gathered_account.py:10
 #: accounts/serializers/automations/change_secret.py:112
 #: accounts/serializers/automations/change_secret.py:132
-#: acls/models/base.py:100 acls/serializers/base.py:56
+#: acls/models/base.py:100 acls/serializers/base.py:76
 #: assets/models/asset/common.py:92 assets/models/asset/common.py:306
 #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19
 #: assets/serializers/label.py:27 audits/models.py:48
@@ -197,29 +197,32 @@ msgstr "仅创建"
 msgid "Asset"
 msgstr "资产"
 
-#: accounts/models/account.py:51 accounts/serializers/account/account.py:204
+#: accounts/models/account.py:53 accounts/models/account.py:113
+#: accounts/serializers/account/account.py:206
+#: accounts/serializers/account/account.py:244
+#: accounts/serializers/account/template.py:16
 #: authentication/serializers/connect_token_secret.py:49
 msgid "Su from"
 msgstr "切换自"
 
-#: accounts/models/account.py:53 settings/serializers/auth/cas.py:20
+#: accounts/models/account.py:55 settings/serializers/auth/cas.py:20
 #: settings/serializers/auth/feishu.py:20 terminal/models/applet/applet.py:29
 msgid "Version"
 msgstr "版本"
 
-#: accounts/models/account.py:55 accounts/serializers/account/account.py:200
-#: users/models/user.py:768
+#: accounts/models/account.py:57 accounts/serializers/account/account.py:202
+#: users/models/user.py:778
 msgid "Source"
 msgstr "来源"
 
-#: accounts/models/account.py:56
+#: accounts/models/account.py:58
 msgid "Source ID"
 msgstr "来源 ID"
 
-#: accounts/models/account.py:59
+#: accounts/models/account.py:61
 #: accounts/serializers/automations/change_secret.py:113
 #: accounts/serializers/automations/change_secret.py:133
-#: acls/models/base.py:102 acls/serializers/base.py:57
+#: acls/models/base.py:102 acls/serializers/base.py:77
 #: assets/serializers/asset/common.py:125 assets/serializers/gateway.py:28
 #: audits/models.py:49 ops/models/base.py:18
 #: perms/models/asset_permission.py:70 perms/serializers/permission.py:39
@@ -228,35 +231,35 @@ msgstr "来源 ID"
 msgid "Account"
 msgstr "账号"
 
-#: accounts/models/account.py:65
+#: accounts/models/account.py:67
 msgid "Can view asset account secret"
 msgstr "可以查看资产账号密码"
 
-#: accounts/models/account.py:66
+#: accounts/models/account.py:68
 msgid "Can view asset history account"
 msgstr "可以查看资产历史账号"
 
-#: accounts/models/account.py:67
+#: accounts/models/account.py:69
 msgid "Can view asset history account secret"
 msgstr "可以查看资产历史账号密码"
 
-#: accounts/models/account.py:68
+#: accounts/models/account.py:70
 msgid "Can verify account"
 msgstr "可以验证账号"
 
-#: accounts/models/account.py:69
+#: accounts/models/account.py:71
 msgid "Can push account"
 msgstr "可以推送账号"
 
-#: accounts/models/account.py:110
+#: accounts/models/account.py:117
 msgid "Account template"
 msgstr "账号模版"
 
-#: accounts/models/account.py:115
+#: accounts/models/account.py:122
 msgid "Can view asset account template secret"
 msgstr "可以查看资产账号模版密码"
 
-#: accounts/models/account.py:116
+#: accounts/models/account.py:123
 msgid "Can change asset account template secret"
 msgstr "可以更改资产账号模版密码"
 
@@ -275,7 +278,8 @@ msgstr "账号备份计划"
 #: accounts/models/automations/backup_account.py:83
 #: assets/models/automations/base.py:115 audits/models.py:55
 #: ops/models/base.py:55 ops/models/celery.py:63 ops/models/job.py:192
-#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:109
+#: ops/templates/ops/celery_task_log.html:75
+#: perms/models/asset_permission.py:72 terminal/models/applet/host.py:137
 #: terminal/models/session/session.py:45
 #: tickets/models/ticket/apply_application.py:30
 #: tickets/models/ticket/apply_asset.py:19
@@ -308,7 +312,7 @@ msgstr "原因"
 #: accounts/models/automations/backup_account.py:99
 #: accounts/serializers/automations/change_secret.py:111
 #: accounts/serializers/automations/change_secret.py:134
-#: ops/serializers/job.py:72 terminal/serializers/session.py:45
+#: ops/serializers/job.py:56 terminal/serializers/session.py:45
 msgid "Is success"
 msgstr "是否成功"
 
@@ -353,7 +357,7 @@ msgid "Can add push account execution"
 msgstr "创建推送账号执行"
 
 #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36
-#: accounts/serializers/account/account.py:394
+#: accounts/serializers/account/account.py:412
 #: accounts/serializers/account/base.py:16
 #: accounts/serializers/automations/change_secret.py:46
 #: authentication/serializers/connect_token_secret.py:41
@@ -398,13 +402,14 @@ msgstr "开始日期"
 #: accounts/models/automations/change_secret.py:91
 #: assets/models/automations/base.py:116 ops/models/base.py:56
 #: ops/models/celery.py:64 ops/models/job.py:193
-#: terminal/models/applet/host.py:110
+#: terminal/models/applet/host.py:138
 msgid "Date finished"
 msgstr "结束日期"
 
 #: accounts/models/automations/change_secret.py:93
-#: accounts/serializers/account/account.py:234 assets/const/automation.py:8
-#: common/const/choices.py:20
+#: accounts/serializers/account/account.py:236 assets/const/automation.py:8
+#: authentication/views/base.py:29 authentication/views/base.py:30
+#: authentication/views/base.py:31 common/const/choices.py:20
 msgid "Error"
 msgstr "错误"
 
@@ -422,13 +427,13 @@ msgstr "最后登录日期"
 
 #: accounts/models/automations/gather_account.py:17
 #: accounts/models/automations/push_account.py:15 accounts/models/base.py:34
-#: acls/serializers/base.py:18 acls/serializers/base.py:49
+#: acls/serializers/base.py:19 acls/serializers/base.py:50
 #: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25
 #: authentication/forms.py:27 authentication/models/temp_token.py:9
 #: authentication/templates/authentication/_msg_different_city.html:9
 #: authentication/templates/authentication/_msg_oauth_bind.html:9
 #: users/forms/profile.py:32 users/forms/profile.py:112
-#: users/models/user.py:715 users/templates/users/_msg_user_created.html:12
+#: users/models/user.py:725 users/templates/users/_msg_user_created.html:12
 #: xpack/plugins/cloud/serializers/account_attrs.py:26
 msgid "Username"
 msgstr "用户名"
@@ -455,8 +460,8 @@ msgid "Triggers"
 msgstr "触发方式"
 
 #: accounts/models/automations/push_account.py:16 acls/models/base.py:81
-#: acls/serializers/base.py:81 acls/serializers/login_acl.py:26
-#: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82
+#: acls/serializers/base.py:57 assets/models/cmd_filter.py:81
+#: audits/models.py:65 audits/serializers.py:82
 #: authentication/serializers/connect_token_secret.py:108
 #: authentication/templates/authentication/_access_key_modal.html:34
 msgid "Action"
@@ -471,14 +476,14 @@ msgid "Verify asset account"
 msgstr "账号验证"
 
 #: accounts/models/base.py:33 acls/models/base.py:75
-#: acls/models/command_acl.py:21 acls/serializers/base.py:34
+#: acls/models/command_acl.py:21 acls/serializers/base.py:35
 #: applications/models.py:9 assets/models/_user.py:22
 #: assets/models/asset/common.py:90 assets/models/asset/common.py:123
 #: assets/models/cmd_filter.py:21 assets/models/domain.py:18
 #: assets/models/group.py:20 assets/models/label.py:18
-#: assets/models/platform.py:13 assets/models/platform.py:89
+#: assets/models/platform.py:13 assets/models/platform.py:81
 #: assets/serializers/asset/common.py:145 assets/serializers/platform.py:92
-#: assets/serializers/platform.py:193
+#: assets/serializers/platform.py:194
 #: authentication/serializers/connect_token_secret.py:102 ops/mixin.py:21
 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57
 #: ops/models/job.py:92 ops/models/playbook.py:23 ops/serializers/job.py:20
@@ -488,7 +493,7 @@ msgstr "账号验证"
 #: terminal/models/component/endpoint.py:90
 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:15
 #: terminal/models/component/terminal.py:84 users/forms/profile.py:33
-#: users/models/group.py:13 users/models/user.py:717
+#: users/models/group.py:13 users/models/user.py:727
 #: xpack/plugins/cloud/models.py:28
 msgid "Name"
 msgstr "名称"
@@ -543,28 +548,28 @@ msgstr ""
 "{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加"
 "密密码"
 
-#: accounts/serializers/account/account.py:28
+#: accounts/serializers/account/account.py:29
 msgid "Push now"
 msgstr "立即推送"
 
-#: accounts/serializers/account/account.py:35
+#: accounts/serializers/account/account.py:36
 msgid "Exist policy"
 msgstr "账号存在策略"
 
-#: accounts/serializers/account/account.py:179 applications/models.py:11
-#: assets/models/label.py:21 assets/models/platform.py:90
+#: accounts/serializers/account/account.py:181 applications/models.py:11
+#: assets/models/label.py:21 assets/models/platform.py:82
 #: assets/serializers/asset/common.py:121 assets/serializers/cagegory.py:8
-#: assets/serializers/platform.py:110 assets/serializers/platform.py:194
+#: assets/serializers/platform.py:110 assets/serializers/platform.py:195
 #: perms/serializers/user_permission.py:26 settings/models.py:35
 #: tickets/models/ticket/apply_application.py:13
 msgid "Category"
 msgstr "类别"
 
-#: accounts/serializers/account/account.py:180
+#: accounts/serializers/account/account.py:182
 #: accounts/serializers/automations/base.py:54 acls/models/command_acl.py:24
 #: acls/serializers/command_acl.py:18 applications/models.py:14
 #: assets/models/_user.py:50 assets/models/automations/base.py:20
-#: assets/models/cmd_filter.py:74 assets/models/platform.py:91
+#: assets/models/cmd_filter.py:74 assets/models/platform.py:83
 #: assets/serializers/asset/common.py:122 assets/serializers/platform.py:94
 #: assets/serializers/platform.py:109 audits/serializers.py:48
 #: authentication/serializers/connect_token_secret.py:115 ops/models/job.py:103
@@ -579,27 +584,27 @@ msgstr "类别"
 msgid "Type"
 msgstr "类型"
 
-#: accounts/serializers/account/account.py:195
+#: accounts/serializers/account/account.py:197
 msgid "Asset not found"
 msgstr "资产不存在"
 
-#: accounts/serializers/account/account.py:201
+#: accounts/serializers/account/account.py:203
 #: accounts/serializers/account/base.py:64
 msgid "Has secret"
 msgstr "已托管密码"
 
-#: accounts/serializers/account/account.py:233 ops/models/celery.py:60
+#: accounts/serializers/account/account.py:235 ops/models/celery.py:60
 #: tickets/models/comment.py:13 tickets/models/ticket/general.py:45
 #: tickets/models/ticket/general.py:279 tickets/serializers/super_ticket.py:14
 #: tickets/serializers/ticket/ticket.py:21
 msgid "State"
 msgstr "状态"
 
-#: accounts/serializers/account/account.py:235
+#: accounts/serializers/account/account.py:237
 msgid "Changed"
 msgstr "已修改"
 
-#: accounts/serializers/account/account.py:241
+#: accounts/serializers/account/account.py:246
 #: accounts/serializers/automations/base.py:22
 #: assets/models/automations/base.py:19
 #: assets/serializers/automations/base.py:20 ops/models/base.py:17
@@ -608,29 +613,29 @@ msgstr "已修改"
 msgid "Assets"
 msgstr "资产"
 
-#: accounts/serializers/account/account.py:293
+#: accounts/serializers/account/account.py:298
 msgid "Account already exists"
 msgstr "账号已存在"
 
-#: accounts/serializers/account/account.py:330
+#: accounts/serializers/account/account.py:348
 #, python-format
 msgid "Asset does not support this secret type: %s"
 msgstr "资产不支持账号类型: %s"
 
-#: accounts/serializers/account/account.py:361
+#: accounts/serializers/account/account.py:379
 msgid "Account has exist"
 msgstr "账号已存在"
 
-#: accounts/serializers/account/account.py:395
+#: accounts/serializers/account/account.py:413
 #: authentication/serializers/connect_token_secret.py:146
 #: authentication/templates/authentication/_access_key_modal.html:30
 #: perms/models/perm_node.py:21 users/serializers/group.py:33
 msgid "ID"
 msgstr "ID"
 
-#: accounts/serializers/account/account.py:402 acls/models/base.py:98
-#: acls/models/login_acl.py:13 acls/serializers/base.py:55
-#: acls/serializers/login_acl.py:22 assets/models/cmd_filter.py:24
+#: accounts/serializers/account/account.py:420 acls/models/base.py:98
+#: acls/models/login_acl.py:13 acls/serializers/base.py:75
+#: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:24
 #: assets/models/label.py:16 audits/models.py:44 audits/models.py:63
 #: audits/models.py:141 authentication/models/connection_token.py:30
 #: authentication/models/sso_token.py:16
@@ -641,12 +646,12 @@ msgstr "ID"
 #: terminal/models/session/session.py:30 terminal/models/session/sharing.py:32
 #: terminal/notifications.py:96 terminal/notifications.py:144
 #: terminal/serializers/command.py:16 tickets/models/comment.py:21
-#: users/const.py:14 users/models/user.py:911 users/models/user.py:942
+#: users/const.py:14 users/models/user.py:921 users/models/user.py:952
 #: users/serializers/group.py:18
 msgid "User"
 msgstr "用户"
 
-#: accounts/serializers/account/account.py:403
+#: accounts/serializers/account/account.py:421
 #: authentication/templates/authentication/_access_key_modal.html:33
 #: terminal/notifications.py:98 terminal/notifications.py:146
 msgid "Date"
@@ -678,10 +683,14 @@ msgid "Key password"
 msgstr "密钥密码"
 
 #: accounts/serializers/account/base.py:80
-#: assets/serializers/asset/common.py:305
+#: assets/serializers/asset/common.py:306
 msgid "Spec info"
 msgstr "特殊信息"
 
+#: accounts/serializers/account/base.py:81
+msgid "Tip: If no username is required for authentication, fill in `null`"
+msgstr "提示: 如果认证时不需要用户名,则填写为 null"
+
 #: accounts/serializers/automations/base.py:23
 #: assets/models/asset/common.py:129 assets/models/automations/base.py:18
 #: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21
@@ -718,8 +727,8 @@ msgstr "自动化任务执行历史"
 
 #: accounts/serializers/automations/change_secret.py:155 audits/const.py:52
 #: audits/models.py:54 audits/signal_handlers/activity_log.py:33
-#: common/const/choices.py:18 ops/const.py:56 ops/serializers/celery.py:39
-#: terminal/const.py:59 terminal/models/session/sharing.py:107
+#: common/const/choices.py:18 ops/const.py:56 ops/serializers/celery.py:40
+#: terminal/const.py:60 terminal/models/session/sharing.py:107
 #: tickets/views/approve.py:114
 msgid "Success"
 msgstr "成功"
@@ -791,14 +800,14 @@ msgstr "优先级"
 msgid "1-100, the lower the value will be match first"
 msgstr "优先级可选范围为 1-100 (数值越小越优先)"
 
-#: acls/models/base.py:82 acls/serializers/base.py:75
-#: acls/serializers/login_acl.py:24 assets/models/cmd_filter.py:86
+#: acls/models/base.py:82 acls/serializers/base.py:95
+#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86
 #: authentication/serializers/connect_token_secret.py:80
 msgid "Reviewers"
 msgstr "审批人"
 
 #: acls/models/base.py:83 authentication/models/access_key.py:17
-#: authentication/models/connection_token.py:49
+#: authentication/models/connection_token.py:50
 #: authentication/templates/authentication/_access_key_modal.html:32
 #: perms/models/asset_permission.py:76 terminal/models/session/sharing.py:27
 #: tickets/const.py:37
@@ -806,7 +815,7 @@ msgid "Active"
 msgstr "激活中"
 
 #: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60
-#: ops/serializers/job.py:71 terminal/const.py:67
+#: ops/serializers/job.py:55 terminal/const.py:68
 #: terminal/models/session/session.py:43 terminal/serializers/command.py:18
 #: terminal/templates/terminal/_msg_command_alert.html:12
 #: terminal/templates/terminal/_msg_command_execute_alert.html:10
@@ -848,7 +857,7 @@ msgstr "命令过滤"
 msgid "Command confirm"
 msgstr "命令复核"
 
-#: acls/models/login_acl.py:16 acls/serializers/login_acl.py:30
+#: acls/models/login_acl.py:16 acls/serializers/login_acl.py:28
 msgid "Rule"
 msgstr "规则"
 
@@ -868,11 +877,11 @@ msgstr "登录资产访问控制"
 msgid "Login asset confirm"
 msgstr "登录资产复核"
 
-#: acls/serializers/base.py:10 acls/serializers/login_acl.py:17
+#: acls/serializers/base.py:11 acls/serializers/login_acl.py:16
 msgid "With * indicating a match all. "
 msgstr "* 表示匹配所有. "
 
-#: acls/serializers/base.py:25
+#: acls/serializers/base.py:26
 msgid ""
 "With * indicating a match all. Such as: 192.168.10.1, 192.168.1.0/24, "
 "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (Domain name "
@@ -881,35 +890,35 @@ msgstr ""
 "* 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:"
 "db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
 
-#: acls/serializers/base.py:40 assets/serializers/asset/host.py:19
+#: acls/serializers/base.py:41 assets/serializers/asset/host.py:19
 msgid "IP/Host"
 msgstr "IP/主机"
 
-#: acls/serializers/base.py:60
+#: acls/serializers/base.py:80
 msgid "User (username)"
 msgstr "用户(用户名)"
 
-#: acls/serializers/base.py:64
+#: acls/serializers/base.py:84
 msgid "Asset (name)"
 msgstr "资产(名称)"
 
-#: acls/serializers/base.py:68
+#: acls/serializers/base.py:88
 msgid "Asset (address)"
 msgstr "资产(地址)"
 
-#: acls/serializers/base.py:72
+#: acls/serializers/base.py:92
 msgid "Account (username)"
 msgstr "账号(用户名)"
 
-#: acls/serializers/base.py:78 acls/serializers/login_acl.py:28
+#: acls/serializers/base.py:98 acls/serializers/login_acl.py:26
 msgid "Reviewers amount"
 msgstr "审批人数量"
 
-#: acls/serializers/base.py:109 tickets/serializers/ticket/ticket.py:76
+#: acls/serializers/base.py:126 tickets/serializers/ticket/ticket.py:76
 msgid "The organization `{}` does not exist"
 msgstr "组织 `{}` 不存在"
 
-#: acls/serializers/base.py:115
+#: acls/serializers/base.py:132
 msgid "None of the reviewers belong to Organization `{}`"
 msgstr "所有复核人都不属于组织 `{}`"
 
@@ -1002,7 +1011,7 @@ msgid "Unable to connect to port {port} on {address}"
 msgstr "无法连接到 {port} 上的端口 {address}"
 
 #: assets/automations/ping_gateway/manager.py:58
-#: authentication/middleware.py:87 xpack/plugins/cloud/providers/fc.py:48
+#: authentication/middleware.py:92 xpack/plugins/cloud/providers/fc.py:48
 msgid "Authentication failed"
 msgstr "认证失败"
 
@@ -1095,7 +1104,7 @@ msgstr "防火墙"
 msgid "Other"
 msgstr "其它"
 
-#: assets/const/types.py:218
+#: assets/const/types.py:223
 msgid "All types"
 msgstr "所有类型"
 
@@ -1133,11 +1142,11 @@ msgstr "SSH公钥"
 #: assets/models/cmd_filter.py:88 assets/models/group.py:23
 #: common/db/models.py:37 ops/models/adhoc.py:27 ops/models/job.py:111
 #: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:38
-#: terminal/models/applet/applet.py:36 terminal/models/applet/applet.py:184
-#: terminal/models/applet/host.py:111 terminal/models/component/endpoint.py:24
+#: terminal/models/applet/applet.py:37 terminal/models/applet/applet.py:218
+#: terminal/models/applet/host.py:139 terminal/models/component/endpoint.py:24
 #: terminal/models/component/endpoint.py:100
 #: terminal/models/session/session.py:47 tickets/models/comment.py:32
-#: tickets/models/ticket/general.py:297 users/models/user.py:756
+#: tickets/models/ticket/general.py:297 users/models/user.py:766
 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:111
 msgid "Comment"
 msgstr "备注"
@@ -1145,18 +1154,18 @@ msgstr "备注"
 #: assets/models/_user.py:28 assets/models/automations/base.py:114
 #: assets/models/cmd_filter.py:41 assets/models/group.py:22
 #: common/db/models.py:35 ops/models/base.py:54 ops/models/job.py:191
-#: users/models/user.py:943
+#: users/models/user.py:953
 msgid "Date created"
 msgstr "创建日期"
 
 #: assets/models/_user.py:29 assets/models/cmd_filter.py:42
-#: common/db/models.py:36 users/models/user.py:777
+#: common/db/models.py:36 users/models/user.py:787
 msgid "Date updated"
 msgstr "更新日期"
 
 #: assets/models/_user.py:30 assets/models/cmd_filter.py:44
 #: assets/models/cmd_filter.py:91 assets/models/group.py:21
-#: common/db/models.py:33 users/models/user.py:763
+#: common/db/models.py:33 users/models/user.py:773
 #: users/serializers/group.py:31
 msgid "Created by"
 msgstr "创建者"
@@ -1246,7 +1255,7 @@ msgstr "端口"
 msgid "Address"
 msgstr "地址"
 
-#: assets/models/asset/common.py:125 assets/models/platform.py:120
+#: assets/models/asset/common.py:125 assets/models/platform.py:112
 #: authentication/serializers/connect_token_secret.py:107
 #: perms/serializers/user_permission.py:24
 #: xpack/plugins/cloud/serializers/account_attrs.py:196
@@ -1255,7 +1264,7 @@ msgstr "系统平台"
 
 #: assets/models/asset/common.py:127 assets/models/domain.py:21
 #: authentication/serializers/connect_token_secret.py:125
-#: perms/serializers/user_permission.py:28
+#: perms/serializers/user_permission.py:29
 msgid "Domain"
 msgstr "网域"
 
@@ -1263,7 +1272,7 @@ msgstr "网域"
 msgid "Labels"
 msgstr "标签管理"
 
-#: assets/models/asset/common.py:132 assets/serializers/asset/common.py:306
+#: assets/models/asset/common.py:132 assets/serializers/asset/common.py:307
 #: assets/serializers/asset/host.py:11
 msgid "Gathered info"
 msgstr "收集资产硬件信息"
@@ -1330,7 +1339,7 @@ msgid "Submit selector"
 msgstr "确认按钮选择器"
 
 #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
-#: assets/serializers/asset/common.py:304 rbac/tree.py:35
+#: assets/serializers/asset/common.py:305 rbac/tree.py:35
 msgid "Accounts"
 msgstr "账号管理"
 
@@ -1348,7 +1357,7 @@ msgstr "资产自动化任务"
 
 #: assets/models/automations/base.py:113 audits/models.py:177
 #: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:184
-#: terminal/models/applet/applet.py:183 terminal/models/applet/host.py:108
+#: terminal/models/applet/applet.py:217 terminal/models/applet/host.py:136
 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18
 #: terminal/serializers/applet_host.py:103 tickets/models/ticket/general.py:283
 #: tickets/serializers/super_ticket.py:13
@@ -1375,7 +1384,7 @@ msgstr "校验日期"
 
 #: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61
 #: perms/serializers/permission.py:32 users/models/group.py:25
-#: users/models/user.py:723
+#: users/models/user.py:733
 msgid "User group"
 msgstr "用户组"
 
@@ -1425,7 +1434,7 @@ msgstr "默认"
 msgid "Default asset group"
 msgstr "默认资产组"
 
-#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:928
+#: assets/models/label.py:15 rbac/const.py:6 users/models/user.py:938
 msgid "System"
 msgstr "系统"
 
@@ -1441,7 +1450,8 @@ msgstr "值"
 #: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13
 #: assets/serializers/platform.py:93
 #: authentication/serializers/connect_token_secret.py:113
-#: common/serializers/common.py:85 settings/serializers/sms.py:7
+#: common/serializers/common.py:85 perms/serializers/user_permission.py:28
+#: settings/serializers/sms.py:7
 msgid "Label"
 msgstr "标签"
 
@@ -1491,102 +1501,102 @@ msgstr "开放的"
 msgid "Setting"
 msgstr "设置"
 
-#: assets/models/platform.py:39 audits/const.py:47 settings/models.py:37
+#: assets/models/platform.py:31 audits/const.py:47 settings/models.py:37
 #: terminal/serializers/applet_host.py:29
 msgid "Enabled"
 msgstr "启用"
 
-#: assets/models/platform.py:40
+#: assets/models/platform.py:32
 msgid "Ansible config"
 msgstr "Ansible 配置"
 
-#: assets/models/platform.py:42 assets/serializers/platform.py:63
+#: assets/models/platform.py:34 assets/serializers/platform.py:63
 msgid "Ping enabled"
 msgstr "启用资产探活"
 
-#: assets/models/platform.py:43 assets/serializers/platform.py:64
+#: assets/models/platform.py:35 assets/serializers/platform.py:64
 msgid "Ping method"
 msgstr "资产探活方式"
 
-#: assets/models/platform.py:44
+#: assets/models/platform.py:36
 msgid "Ping params"
 msgstr "资产探活参数"
 
-#: assets/models/platform.py:46 assets/models/platform.py:70
+#: assets/models/platform.py:38 assets/models/platform.py:62
 #: assets/serializers/platform.py:65
 msgid "Gather facts enabled"
 msgstr "启用收集资产信息"
 
-#: assets/models/platform.py:48 assets/models/platform.py:72
+#: assets/models/platform.py:40 assets/models/platform.py:64
 #: assets/serializers/platform.py:66
 msgid "Gather facts method"
 msgstr "收集信息方式"
 
-#: assets/models/platform.py:50 assets/models/platform.py:74
+#: assets/models/platform.py:42 assets/models/platform.py:66
 msgid "Gather facts params"
 msgstr "收集信息参数"
 
-#: assets/models/platform.py:52 assets/serializers/platform.py:69
+#: assets/models/platform.py:44 assets/serializers/platform.py:69
 msgid "Change secret enabled"
 msgstr "启用改密"
 
-#: assets/models/platform.py:54 assets/serializers/platform.py:70
+#: assets/models/platform.py:46 assets/serializers/platform.py:70
 msgid "Change secret method"
 msgstr "改密方式"
 
-#: assets/models/platform.py:56
+#: assets/models/platform.py:48
 msgid "Change secret params"
 msgstr "改密参数"
 
-#: assets/models/platform.py:58 assets/serializers/platform.py:71
+#: assets/models/platform.py:50 assets/serializers/platform.py:71
 msgid "Push account enabled"
 msgstr "启用账号推送"
 
-#: assets/models/platform.py:60 assets/serializers/platform.py:72
+#: assets/models/platform.py:52 assets/serializers/platform.py:72
 msgid "Push account method"
 msgstr "账号推送方式"
 
-#: assets/models/platform.py:62
+#: assets/models/platform.py:54
 msgid "Push account params"
 msgstr "账号推送参数"
 
-#: assets/models/platform.py:64 assets/serializers/platform.py:67
+#: assets/models/platform.py:56 assets/serializers/platform.py:67
 msgid "Verify account enabled"
 msgstr "开启账号验证"
 
-#: assets/models/platform.py:66 assets/serializers/platform.py:68
+#: assets/models/platform.py:58 assets/serializers/platform.py:68
 msgid "Verify account method"
 msgstr "账号验证方式"
 
-#: assets/models/platform.py:68
+#: assets/models/platform.py:60
 msgid "Verify account params"
 msgstr "账号验证参数"
 
-#: assets/models/platform.py:92 tickets/models/ticket/general.py:300
+#: assets/models/platform.py:84 tickets/models/ticket/general.py:300
 msgid "Meta"
 msgstr "元数据"
 
-#: assets/models/platform.py:93
+#: assets/models/platform.py:85
 msgid "Internal"
 msgstr "内置"
 
-#: assets/models/platform.py:97 assets/serializers/platform.py:108
+#: assets/models/platform.py:89 assets/serializers/platform.py:108
 msgid "Charset"
 msgstr "编码"
 
-#: assets/models/platform.py:99 assets/serializers/platform.py:135
+#: assets/models/platform.py:91 assets/serializers/platform.py:136
 msgid "Domain enabled"
 msgstr "启用网域"
 
-#: assets/models/platform.py:101 assets/serializers/platform.py:134
+#: assets/models/platform.py:93 assets/serializers/platform.py:135
 msgid "Su enabled"
 msgstr "启用账号切换"
 
-#: assets/models/platform.py:102 assets/serializers/platform.py:114
+#: assets/models/platform.py:94 assets/serializers/platform.py:114
 msgid "Su method"
 msgstr "账号切换方式"
 
-#: assets/models/platform.py:103 assets/serializers/platform.py:117
+#: assets/models/platform.py:95 assets/serializers/platform.py:117
 msgid "Custom fields"
 msgstr "自定义属性"
 
@@ -1614,23 +1624,29 @@ msgid "Node path"
 msgstr "节点路径"
 
 #: assets/serializers/asset/common.py:144
-#: assets/serializers/asset/common.py:307
+#: assets/serializers/asset/common.py:308
 msgid "Auto info"
 msgstr "自动化信息"
 
-#: assets/serializers/asset/common.py:226
+#: assets/serializers/asset/common.py:227
 msgid "Platform not exist"
 msgstr "平台不存在"
 
-#: assets/serializers/asset/common.py:262
+#: assets/serializers/asset/common.py:263
 msgid "port out of range (1-65535)"
 msgstr "端口超出范围 (1-65535)"
 
-#: assets/serializers/asset/common.py:269
+#: assets/serializers/asset/common.py:270
 msgid "Protocol is required: {}"
 msgstr "协议是必填的: {}"
 
-#: assets/serializers/asset/database.py:25 common/serializers/fields.py:103
+#: assets/serializers/asset/database.py:13
+#, fuzzy
+#| msgid "Default storage"
+msgid "Default database"
+msgstr "默认存储"
+
+#: assets/serializers/asset/database.py:28 common/serializers/fields.py:103
 #: tickets/serializers/ticket/common.py:58
 #: xpack/plugins/cloud/serializers/account_attrs.py:56
 #: xpack/plugins/cloud/serializers/account_attrs.py:79
@@ -1748,15 +1764,15 @@ msgstr ""
 msgid "Automation"
 msgstr "自动化"
 
-#: assets/serializers/platform.py:136
+#: assets/serializers/platform.py:137
 msgid "Default Domain"
 msgstr "默认网域"
 
-#: assets/serializers/platform.py:145
+#: assets/serializers/platform.py:146
 msgid "type is required"
 msgstr "类型 该字段是必填项。"
 
-#: assets/serializers/platform.py:182
+#: assets/serializers/platform.py:183
 msgid "Protocols is required"
 msgstr "协议是必填的"
 
@@ -1910,7 +1926,7 @@ msgstr "会话日志"
 msgid "Login log"
 msgstr "登录日志"
 
-#: audits/const.py:42 terminal/models/applet/host.py:112
+#: audits/const.py:42 terminal/models/applet/host.py:140
 #: terminal/models/component/task.py:24
 msgid "Task"
 msgstr "任务"
@@ -2006,7 +2022,7 @@ msgstr "用户代理"
 
 #: audits/models.py:169 audits/serializers.py:47
 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
-#: users/forms/profile.py:65 users/models/user.py:740
+#: users/forms/profile.py:65 users/models/user.py:750
 #: users/serializers/profile.py:126
 msgid "MFA"
 msgstr "MFA"
@@ -2060,22 +2076,24 @@ msgid "Auth Token"
 msgstr "认证令牌"
 
 #: audits/signal_handlers/login_log.py:31 authentication/notifications.py:73
-#: authentication/views/login.py:74 authentication/views/wecom.py:177
+#: authentication/views/login.py:74 authentication/views/wecom.py:159
 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10
-#: users/models/user.py:778
+#: users/models/user.py:680 users/models/user.py:788
 msgid "WeCom"
 msgstr "企业微信"
 
-#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:144
+#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:123
 #: authentication/views/login.py:86 notifications/backends/__init__.py:14
 #: settings/serializers/auth/feishu.py:10
-#: settings/serializers/auth/feishu.py:13 users/models/user.py:780
+#: settings/serializers/auth/feishu.py:13 users/models/user.py:682
+#: users/models/user.py:790
 msgid "FeiShu"
 msgstr "飞书"
 
-#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:179
+#: audits/signal_handlers/login_log.py:33 authentication/views/dingtalk.py:160
 #: authentication/views/login.py:80 notifications/backends/__init__.py:12
-#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:779
+#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:681
+#: users/models/user.py:789
 msgid "DingTalk"
 msgstr "钉钉"
 
@@ -2092,19 +2110,19 @@ msgstr "清理审计会话任务日志"
 msgid "This action require verify your MFA"
 msgstr "该操作需要验证您的 MFA, 请先开启并配置"
 
-#: authentication/api/connection_token.py:296
+#: authentication/api/connection_token.py:303
 msgid "Account not found"
 msgstr "账号未找到"
 
-#: authentication/api/connection_token.py:299
+#: authentication/api/connection_token.py:306
 msgid "Permission expired"
 msgstr "授权已过期"
 
-#: authentication/api/connection_token.py:311
+#: authentication/api/connection_token.py:318
 msgid "ACL action is reject"
 msgstr "ACL 动作是拒绝"
 
-#: authentication/api/connection_token.py:315
+#: authentication/api/connection_token.py:322
 msgid "ACL action is review"
 msgstr "ACL 动作是复核"
 
@@ -2112,7 +2130,7 @@ msgstr "ACL 动作是复核"
 msgid "Current user not support mfa type: {}"
 msgstr "当前用户不支持 MFA 类型: {}"
 
-#: authentication/api/password.py:31 terminal/api/session/session.py:247
+#: authentication/api/password.py:31 terminal/api/session/session.py:249
 #: users/views/profile/reset.py:44
 msgid "User does not exist: {}"
 msgstr "用户不存在: {}"
@@ -2326,21 +2344,21 @@ msgstr "手机号没有设置"
 msgid "SSO auth closed"
 msgstr "SSO 认证关闭了"
 
-#: authentication/errors/mfa.py:18 authentication/views/wecom.py:79
+#: authentication/errors/mfa.py:18 authentication/views/wecom.py:61
 msgid "WeCom is already bound"
 msgstr "企业微信已经绑定"
 
-#: authentication/errors/mfa.py:23 authentication/views/wecom.py:236
-#: authentication/views/wecom.py:290
+#: authentication/errors/mfa.py:23 authentication/views/wecom.py:202
+#: authentication/views/wecom.py:244
 msgid "WeCom is not bound"
 msgstr "没有绑定企业微信"
 
-#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:242
-#: authentication/views/dingtalk.py:296
+#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:210
+#: authentication/views/dingtalk.py:252
 msgid "DingTalk is not bound"
 msgstr "钉钉没有绑定"
 
-#: authentication/errors/mfa.py:33 authentication/views/feishu.py:203
+#: authentication/errors/mfa.py:33 authentication/views/feishu.py:167
 msgid "FeiShu is not bound"
 msgstr "没有绑定飞书"
 
@@ -2348,15 +2366,15 @@ msgstr "没有绑定飞书"
 msgid "Your password is invalid"
 msgstr "您的密码无效"
 
-#: authentication/errors/redirect.py:85 authentication/mixins.py:307
+#: authentication/errors/redirect.py:85 authentication/mixins.py:316
 msgid "Your password is too simple, please change it for security"
 msgstr "你的密码过于简单,为了安全,请修改"
 
-#: authentication/errors/redirect.py:93 authentication/mixins.py:314
+#: authentication/errors/redirect.py:93 authentication/mixins.py:323
 msgid "You should to change your password before login"
 msgstr "登录完成前,请先修改密码"
 
-#: authentication/errors/redirect.py:101 authentication/mixins.py:321
+#: authentication/errors/redirect.py:101 authentication/mixins.py:330
 msgid "Your password has expired, please reset before logging in"
 msgstr "您的密码已过期,先修改再登录"
 
@@ -2453,15 +2471,21 @@ msgstr "设置手机号码启用"
 msgid "Clear phone number to disable"
 msgstr "清空手机号码禁用"
 
-#: authentication/middleware.py:88 settings/utils/ldap.py:652
+#: authentication/middleware.py:93 settings/utils/ldap.py:652
 msgid "Authentication failed (before login check failed): {}"
 msgstr "认证失败(登录前检查失败): {}"
 
-#: authentication/mixins.py:257
+#: authentication/mixins.py:91
+msgid ""
+"The administrator has enabled 'Only allow login from user source'. \n"
+"            The current user source is {}. Please contact the administrator."
+msgstr "管理员已开启'仅允许从用户来源登录',当前用户来源为{},请联系管理员。"
+
+#: authentication/mixins.py:266
 msgid "The MFA type ({}) is not enabled"
 msgstr "该 MFA ({}) 方式没有启用"
 
-#: authentication/mixins.py:297
+#: authentication/mixins.py:306
 msgid "Please change your password"
 msgstr "请修改密码"
 
@@ -2475,7 +2499,7 @@ msgid "Input username"
 msgstr "自定义用户名"
 
 #: authentication/models/connection_token.py:38
-#: authentication/serializers/connection_token.py:17
+#: authentication/serializers/connection_token.py:20
 msgid "Input secret"
 msgstr "自定义密码"
 
@@ -2493,34 +2517,40 @@ msgid "Asset display"
 msgstr "资产名称"
 
 #: authentication/models/connection_token.py:43
+#, fuzzy
+#| msgid "Disable"
+msgid "Reusable"
+msgstr "禁用"
+
+#: authentication/models/connection_token.py:44
 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:74
 #: tickets/models/ticket/apply_application.py:31
-#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:761
+#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:771
 msgid "Date expired"
 msgstr "失效日期"
 
-#: authentication/models/connection_token.py:47
+#: authentication/models/connection_token.py:48
 #: perms/models/asset_permission.py:77
 msgid "From ticket"
 msgstr "来自工单"
 
-#: authentication/models/connection_token.py:53
+#: authentication/models/connection_token.py:54
 msgid "Connection token"
 msgstr "连接令牌"
 
-#: authentication/models/connection_token.py:55
+#: authentication/models/connection_token.py:56
 msgid "Can view connection token secret"
 msgstr "可以查看连接令牌密文"
 
-#: authentication/models/connection_token.py:102
+#: authentication/models/connection_token.py:103
 msgid "Connection token inactive"
 msgstr "连接令牌未激活"
 
-#: authentication/models/connection_token.py:105
+#: authentication/models/connection_token.py:106
 msgid "Connection token expired at: {}"
 msgstr "连接令牌过期: {}"
 
-#: authentication/models/connection_token.py:108
+#: authentication/models/connection_token.py:109
 msgid "No user or invalid user"
 msgstr "没有用户或用户失效"
 
@@ -2572,15 +2602,15 @@ msgstr "组件"
 msgid "Expired now"
 msgstr "立刻过期"
 
-#: authentication/serializers/connection_token.py:15
+#: authentication/serializers/connection_token.py:18
 msgid "Expired time"
 msgstr "过期时间"
 
-#: authentication/serializers/connection_token.py:19
+#: authentication/serializers/connection_token.py:22
 msgid "Ticket info"
 msgstr "工单信息"
 
-#: authentication/serializers/connection_token.py:20
+#: authentication/serializers/connection_token.py:23
 #: perms/models/asset_permission.py:71 perms/serializers/permission.py:36
 #: perms/serializers/permission.py:69
 #: tickets/models/ticket/apply_application.py:28
@@ -2588,17 +2618,21 @@ msgstr "工单信息"
 msgid "Actions"
 msgstr "动作"
 
-#: authentication/serializers/connection_token.py:41
+#: authentication/serializers/connection_token.py:44
 #: perms/serializers/permission.py:38 perms/serializers/permission.py:70
 #: users/serializers/user.py:97 users/serializers/user.py:172
 msgid "Is expired"
 msgstr "已过期"
 
+#: authentication/serializers/connection_token.py:79
+msgid "Reusable connection token is not allowed, global setting not enabled"
+msgstr ""
+
 #: authentication/serializers/password_mfa.py:16
 #: authentication/serializers/password_mfa.py:24
 #: notifications/backends/__init__.py:10 settings/serializers/email.py:19
 #: settings/serializers/email.py:50 users/forms/profile.py:102
-#: users/forms/profile.py:106 users/models/user.py:719
+#: users/forms/profile.py:106 users/models/user.py:729
 #: users/templates/users/forgot_password.html:116
 #: users/views/profile/reset.py:73
 msgid "Email"
@@ -2688,7 +2722,7 @@ msgstr "代码错误"
 #: authentication/templates/authentication/_msg_reset_password_code.html:9
 #: authentication/templates/authentication/_msg_rest_password_success.html:2
 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
-#: jumpserver/conf.py:417
+#: jumpserver/conf.py:419
 #: perms/templates/perms/_msg_item_permissions_expire.html:3
 #: perms/templates/perms/_msg_permed_items_expire.html:3
 #: tickets/templates/tickets/approve_check_password.html:33
@@ -2832,76 +2866,77 @@ msgstr "复制成功"
 msgid "LAN"
 msgstr "局域网"
 
-#: authentication/views/dingtalk.py:41
+#: authentication/views/base.py:64
+#: perms/templates/perms/_msg_permed_items_expire.html:21
+msgid "If you have any question, please contact the administrator"
+msgstr "如果有疑问或需求,请联系系统管理员"
+
+#: authentication/views/dingtalk.py:42
 msgid "DingTalk Error, Please contact your system administrator"
 msgstr "钉钉错误,请联系系统管理员"
 
-#: authentication/views/dingtalk.py:44
+#: authentication/views/dingtalk.py:45 authentication/views/dingtalk.py:209
 msgid "DingTalk Error"
 msgstr "钉钉错误"
 
-#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:51
-#: authentication/views/wecom.py:55
+#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:51
+#: authentication/views/wecom.py:57
 msgid ""
 "The system configuration is incorrect. Please contact your administrator"
 msgstr "企业配置错误,请联系系统管理员"
 
-#: authentication/views/dingtalk.py:80
+#: authentication/views/dingtalk.py:61
 msgid "DingTalk is already bound"
 msgstr "钉钉已经绑定"
 
-#: authentication/views/dingtalk.py:148 authentication/views/wecom.py:147
+#: authentication/views/dingtalk.py:129 authentication/views/wecom.py:129
 msgid "Invalid user_id"
 msgstr "无效的 user_id"
 
-#: authentication/views/dingtalk.py:164
+#: authentication/views/dingtalk.py:145
 msgid "DingTalk query user failed"
 msgstr "钉钉查询用户失败"
 
-#: authentication/views/dingtalk.py:173
+#: authentication/views/dingtalk.py:154
 msgid "The DingTalk is already bound to another user"
 msgstr "该钉钉已经绑定其他用户"
 
-#: authentication/views/dingtalk.py:180
+#: authentication/views/dingtalk.py:161
 msgid "Binding DingTalk successfully"
 msgstr "绑定 钉钉 成功"
 
-#: authentication/views/dingtalk.py:236 authentication/views/dingtalk.py:290
+#: authentication/views/dingtalk.py:211 authentication/views/dingtalk.py:246
 msgid "Failed to get user from DingTalk"
 msgstr "从钉钉获取用户失败"
 
-#: authentication/views/dingtalk.py:243 authentication/views/dingtalk.py:297
+#: authentication/views/dingtalk.py:253
 msgid "Please login with a password and then bind the DingTalk"
 msgstr "请使用密码登录,然后绑定钉钉"
 
-#: authentication/views/feishu.py:39
+#: authentication/views/feishu.py:39 authentication/views/feishu.py:166
 msgid "FeiShu Error"
 msgstr "飞书错误"
 
-#: authentication/views/feishu.py:87
+#: authentication/views/feishu.py:67
 msgid "FeiShu is already bound"
 msgstr "飞书已经绑定"
 
-#: authentication/views/feishu.py:129
+#: authentication/views/feishu.py:108
 msgid "FeiShu query user failed"
 msgstr "飞书查询用户失败"
 
-#: authentication/views/feishu.py:138
+#: authentication/views/feishu.py:117
 msgid "The FeiShu is already bound to another user"
 msgstr "该飞书已经绑定其他用户"
 
-#: authentication/views/feishu.py:145
+#: authentication/views/feishu.py:124
 msgid "Binding FeiShu successfully"
 msgstr "绑定 飞书 成功"
 
-#: authentication/views/feishu.py:197
+#: authentication/views/feishu.py:168
 msgid "Failed to get user from FeiShu"
 msgstr "从飞书获取用户失败"
 
-#: authentication/views/feishu.py:204
-msgid "Please login with a password and then bind the FeiShu"
-msgstr "请使用密码登录,然后绑定飞书"
-
 #: authentication/views/login.py:182
 msgid "Redirecting"
 msgstr "跳转中"
@@ -2938,31 +2973,31 @@ msgstr "退出登录成功"
 msgid "Logout success, return login page"
 msgstr "退出登录成功,返回到登录页面"
 
-#: authentication/views/wecom.py:40
+#: authentication/views/wecom.py:42
 msgid "WeCom Error, Please contact your system administrator"
 msgstr "企业微信错误,请联系系统管理员"
 
-#: authentication/views/wecom.py:43
+#: authentication/views/wecom.py:45 authentication/views/wecom.py:201
 msgid "WeCom Error"
 msgstr "企业微信错误"
 
-#: authentication/views/wecom.py:162
+#: authentication/views/wecom.py:144
 msgid "WeCom query user failed"
 msgstr "企业微信查询用户失败"
 
-#: authentication/views/wecom.py:171
+#: authentication/views/wecom.py:153
 msgid "The WeCom is already bound to another user"
 msgstr "该企业微信已经绑定其他用户"
 
-#: authentication/views/wecom.py:178
+#: authentication/views/wecom.py:160
 msgid "Binding WeCom successfully"
 msgstr "绑定 企业微信 成功"
 
-#: authentication/views/wecom.py:230 authentication/views/wecom.py:284
+#: authentication/views/wecom.py:203 authentication/views/wecom.py:238
 msgid "Failed to get user from WeCom"
 msgstr "从企业微信获取用户失败"
 
-#: authentication/views/wecom.py:237 authentication/views/wecom.py:291
+#: authentication/views/wecom.py:245
 msgid "Please login with a password and then bind the WeCom"
 msgstr "请使用密码登录,然后绑定企业微信"
 
@@ -2982,7 +3017,7 @@ msgstr "定时触发"
 msgid "Ready"
 msgstr "准备"
 
-#: common/const/choices.py:16 terminal/const.py:58 tickets/const.py:29
+#: common/const/choices.py:16 terminal/const.py:59 tickets/const.py:29
 #: tickets/const.py:39
 msgid "Pending"
 msgstr "待定的"
@@ -3045,7 +3080,7 @@ msgstr "忽略的"
 msgid "discard time"
 msgstr "忽略时间"
 
-#: common/db/models.py:34 users/models/user.py:764
+#: common/db/models.py:34 users/models/user.py:774
 msgid "Updated by"
 msgstr "最后更新者"
 
@@ -3073,6 +3108,12 @@ msgstr "解析文件错误: {}"
 msgid "Invalid excel file"
 msgstr "无效的 excel 文件"
 
+#: common/drf/renders/base.py:209
+msgid ""
+"{} - The encryption password has not been set - please go to personal "
+"information -> file encryption password to set the encryption password"
+msgstr "{} - 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加密密码"
+
 #: common/exceptions.py:15
 #, python-format
 msgid "%s object does not exist."
@@ -3114,7 +3155,7 @@ msgstr "不支持 Elasticsearch8"
 msgid "Network error, please contact system administrator"
 msgstr "网络错误,请联系系统管理员"
 
-#: common/sdk/im/wecom/__init__.py:15
+#: common/sdk/im/wecom/__init__.py:16
 msgid "WeCom error, please contact system administrator"
 msgstr "企业微信错误,请联系系统管理员"
 
@@ -3243,11 +3284,11 @@ msgstr "导出搜素: %s"
 msgid "User %s view/export secret"
 msgstr "用户 %s 查看/导出 了密码"
 
-#: jumpserver/conf.py:416
+#: jumpserver/conf.py:418
 msgid "Create account successfully"
 msgstr "创建账号成功"
 
-#: jumpserver/conf.py:418
+#: jumpserver/conf.py:420
 msgid "Your account has been created successfully"
 msgstr "你的账号已创建成功"
 
@@ -3310,15 +3351,15 @@ msgstr "系统信息"
 msgid "Publish the station message"
 msgstr "发布站内消息"
 
-#: ops/ansible/inventory.py:77
+#: ops/ansible/inventory.py:82
 msgid "No account available"
 msgstr "无可用账号"
 
-#: ops/ansible/inventory.py:236
+#: ops/ansible/inventory.py:247
 msgid "Ansible disabled"
 msgstr "Ansible 已禁用"
 
-#: ops/ansible/inventory.py:252
+#: ops/ansible/inventory.py:263
 msgid "Skip hosts below:"
 msgstr "跳过以下主机: "
 
@@ -3584,15 +3625,15 @@ msgstr "CPU 使用率超过 {max_threshold}: => {value}"
 msgid "Run after save"
 msgstr "保存后执行"
 
-#: ops/serializers/job.py:70
+#: ops/serializers/job.py:54
 msgid "Job type"
 msgstr "任务类型"
 
-#: ops/serializers/job.py:73 terminal/serializers/session.py:49
+#: ops/serializers/job.py:57 terminal/serializers/session.py:49
 msgid "Is finished"
 msgstr "是否完成"
 
-#: ops/serializers/job.py:74
+#: ops/serializers/job.py:58
 msgid "Time cost"
 msgstr "花费时间"
 
@@ -3624,6 +3665,10 @@ msgstr "清理异常作业"
 msgid "Task log"
 msgstr "任务列表"
 
+#: ops/templates/ops/celery_task_log.html:71
+msgid "Task name"
+msgstr "任务名称"
+
 #: ops/variables.py:24
 msgid "The current user`s username of JumpServer"
 msgstr "JumpServer 当前用户的用户名"
@@ -3809,10 +3854,6 @@ msgstr ""
 "        以下 %(item_type)s 即将在 %(count)s 天后过期\n"
 "    "
 
-#: perms/templates/perms/_msg_permed_items_expire.html:21
-msgid "If you have any question, please contact the administrator"
-msgstr "如果有疑问或需求,请联系系统管理员"
-
 #: rbac/api/role.py:35
 msgid "Internal role, can't be destroy"
 msgstr "内部角色,不能删除"
@@ -3891,7 +3932,7 @@ msgid "Scope"
 msgstr "范围"
 
 #: rbac/models/role.py:46 rbac/models/rolebinding.py:52
-#: users/models/user.py:727
+#: users/models/user.py:737
 msgid "Role"
 msgstr "角色"
 
@@ -3907,21 +3948,21 @@ msgstr "组织角色"
 msgid "Role binding"
 msgstr "角色绑定"
 
-#: rbac/models/rolebinding.py:145
+#: rbac/models/rolebinding.py:153
 msgid "All organizations"
 msgstr "所有组织"
 
-#: rbac/models/rolebinding.py:174
+#: rbac/models/rolebinding.py:182
 msgid ""
 "User last role in org, can not be delete, you can remove user from org "
 "instead"
 msgstr "用户最后一个角色,不能删除,你可以将用户从组织移除"
 
-#: rbac/models/rolebinding.py:181
+#: rbac/models/rolebinding.py:189
 msgid "Organization role binding"
 msgstr "组织角色绑定"
 
-#: rbac/models/rolebinding.py:196
+#: rbac/models/rolebinding.py:204
 msgid "System role binding"
 msgstr "系统角色绑定"
 
@@ -3997,8 +4038,8 @@ msgstr "任务中心"
 msgid "My assets"
 msgstr "我的资产"
 
-#: rbac/tree.py:56 terminal/models/applet/applet.py:43
-#: terminal/models/applet/applet.py:180 terminal/models/applet/host.py:28
+#: rbac/tree.py:56 terminal/models/applet/applet.py:44
+#: terminal/models/applet/applet.py:214 terminal/models/applet/host.py:28
 #: terminal/serializers/applet.py:15
 msgid "Applet"
 msgstr "远程应用"
@@ -4854,24 +4895,38 @@ msgid "Only single device login"
 msgstr "仅一台设备登录"
 
 #: settings/serializers/security.py:97
-msgid "Next device login, pre login will be logout"
-msgstr "下个设备登录,上次登录会被顶掉"
+msgid ""
+"After the user logs in on the new device, other logged-in devices will "
+"automatically log out"
+msgstr "用户在新设备登录后,其他已登录的设备会自动退出"
 
 #: settings/serializers/security.py:100
 msgid "Only exist user login"
 msgstr "仅已存在用户登录"
 
 #: settings/serializers/security.py:101
-msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet"
-msgstr "开启后,如果系统中不存在该用户,CAS、OIDC 登录将会失败"
+msgid ""
+"If enabled, non-existent users will not be allowed to log in; if disabled, "
+"users of other authentication methods except local authentication methods "
+"are allowed to log in and automatically create users (if the user does not "
+"exist)"
+msgstr ""
+"如果开启,不存在的用户将不被允许登录;如果关闭,除本地认证方式外,其他认证方"
+"式的用户都允许登录并自动创建用户(如果用户不存在)"
 
 #: settings/serializers/security.py:104
 msgid "Only from source login"
 msgstr "仅从用户来源登录"
 
 #: settings/serializers/security.py:105
-msgid "Only log in from the user source property"
-msgstr "开启后,如果用户来源为本地,CAS、OIDC 登录将会失败"
+msgid ""
+"If it is enabled, the user will only authenticate to the source when logging "
+"in; if it is disabled, the user will authenticate all the enabled "
+"authentication methods in a certain order when logging in, and as long as "
+"one of the authentication methods is successful, they can log in directly"
+msgstr ""
+"如果开启,用户登录时仅会向来源端进行认证;如果关闭,用户登录时会按照一定的顺"
+"序对所有已开启的认证方式进行顺序认证,只要有一个认证成功就可以直接登录"
 
 #: settings/serializers/security.py:109
 msgid "MFA verify TTL"
@@ -5403,15 +5458,15 @@ msgstr "测试失败: 账号无效"
 msgid "Have online sessions"
 msgstr "有在线会话"
 
-#: terminal/api/session/session.py:239
+#: terminal/api/session/session.py:241
 msgid "Session does not exist: {}"
 msgstr "会话不存在: {}"
 
-#: terminal/api/session/session.py:242
+#: terminal/api/session/session.py:244
 msgid "Session is finished or the protocol not supported"
 msgstr "会话已经完成或协议不支持"
 
-#: terminal/api/session/session.py:255
+#: terminal/api/session/session.py:257
 msgid "User does not have permission"
 msgstr "用户没有权限"
 
@@ -5464,7 +5519,7 @@ msgstr "严重"
 msgid "High"
 msgstr "较高"
 
-#: terminal/const.py:32 terminal/const.py:65
+#: terminal/const.py:32 terminal/const.py:66
 #: users/templates/users/reset_password.html:50
 msgid "Normal"
 msgstr "正常"
@@ -5473,19 +5528,19 @@ msgstr "正常"
 msgid "Offline"
 msgstr "离线"
 
-#: terminal/const.py:61
+#: terminal/const.py:62
 msgid "Mismatch"
 msgstr "未匹配"
 
-#: terminal/const.py:66
+#: terminal/const.py:67
 msgid "Tunnel"
 msgstr "隧道"
 
-#: terminal/const.py:71
+#: terminal/const.py:72
 msgid "Read Only"
 msgstr "只读"
 
-#: terminal/const.py:72
+#: terminal/const.py:73
 msgid "Writable"
 msgstr "读写"
 
@@ -5502,31 +5557,37 @@ msgid "Author"
 msgstr "作者"
 
 #: terminal/models/applet/applet.py:35
+#, fuzzy
+#| msgid "Can push account"
+msgid "Can concurrent"
+msgstr "可以推送账号"
+
+#: terminal/models/applet/applet.py:36
 msgid "Tags"
 msgstr "标签"
 
-#: terminal/models/applet/applet.py:39 terminal/serializers/storage.py:157
+#: terminal/models/applet/applet.py:40 terminal/serializers/storage.py:157
 msgid "Hosts"
 msgstr "主机"
 
-#: terminal/models/applet/applet.py:84
+#: terminal/models/applet/applet.py:85
 msgid "Applet pkg not valid, Missing file {}"
 msgstr "Applet pkg 无效,缺少文件 {}"
 
-#: terminal/models/applet/applet.py:103
+#: terminal/models/applet/applet.py:104
 msgid "Load platform.yml failed: {}"
 msgstr ""
 
-#: terminal/models/applet/applet.py:106
+#: terminal/models/applet/applet.py:107
 msgid "Only support custom platform"
 msgstr ""
 
-#: terminal/models/applet/applet.py:111
+#: terminal/models/applet/applet.py:112
 msgid "Missing type in platform.yml"
 msgstr ""
 
-#: terminal/models/applet/applet.py:182 terminal/models/applet/host.py:34
-#: terminal/models/applet/host.py:106
+#: terminal/models/applet/applet.py:216 terminal/models/applet/host.py:34
+#: terminal/models/applet/host.py:134
 msgid "Hosting"
 msgstr "宿主机"
 
@@ -5546,7 +5607,7 @@ msgstr "初始化日期"
 msgid "Date synced"
 msgstr "同步日期"
 
-#: terminal/models/applet/host.py:107
+#: terminal/models/applet/host.py:135
 msgid "Initial"
 msgstr "初始化"
 
@@ -5979,7 +6040,7 @@ msgstr "周期清理终端状态"
 
 #: terminal/tasks.py:37
 msgid "Clean orphan session"
-msgstr "清除孤儿会话"
+msgstr "清除离线会话"
 
 #: terminal/tasks.py:56
 msgid "Upload session replay to external storage"
@@ -5993,6 +6054,12 @@ msgstr "运行应用机部署"
 msgid "Install applet"
 msgstr "安装应用"
 
+#: terminal/tasks.py:104
+#, fuzzy
+#| msgid "Gather assets accounts"
+msgid "Generate applet host accounts"
+msgstr "收集资产上的账号"
+
 #: terminal/templates/terminal/_msg_command_alert.html:10
 msgid "view"
 msgstr "查看"
@@ -6349,7 +6416,7 @@ msgstr "无效的审批动作"
 msgid "This user is not authorized to approve this ticket"
 msgstr "此用户无权审批此工单"
 
-#: users/api/user.py:182
+#: users/api/user.py:185
 msgid "Could not reset self otp, use profile reset instead"
 msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置"
 
@@ -6460,7 +6527,7 @@ msgstr "不能和原来的密钥相同"
 msgid "Not a valid ssh public key"
 msgstr "SSH密钥不合法"
 
-#: users/forms/profile.py:170 users/models/user.py:750
+#: users/forms/profile.py:170 users/models/user.py:760
 msgid "Public key"
 msgstr "SSH公钥"
 
@@ -6468,68 +6535,68 @@ msgstr "SSH公钥"
 msgid "Force enable"
 msgstr "强制启用"
 
-#: users/models/user.py:729 users/serializers/user.py:171
+#: users/models/user.py:739 users/serializers/user.py:171
 msgid "Is service account"
 msgstr "服务账号"
 
-#: users/models/user.py:731
+#: users/models/user.py:741
 msgid "Avatar"
 msgstr "头像"
 
-#: users/models/user.py:734
+#: users/models/user.py:744
 msgid "Wechat"
 msgstr "微信"
 
-#: users/models/user.py:737 users/serializers/user.py:109
+#: users/models/user.py:747 users/serializers/user.py:109
 msgid "Phone"
 msgstr "手机"
 
-#: users/models/user.py:743
+#: users/models/user.py:753
 msgid "OTP secret key"
 msgstr "OTP 密钥"
 
-#: users/models/user.py:747
+#: users/models/user.py:757
 msgid "Private key"
 msgstr "ssh私钥"
 
-#: users/models/user.py:753
+#: users/models/user.py:763
 msgid "Secret key"
 msgstr "Secret key"
 
-#: users/models/user.py:758 users/serializers/profile.py:149
+#: users/models/user.py:768 users/serializers/profile.py:149
 #: users/serializers/user.py:168
 msgid "Is first login"
 msgstr "首次登录"
 
-#: users/models/user.py:772
+#: users/models/user.py:782
 msgid "Date password last updated"
 msgstr "最后更新密码日期"
 
-#: users/models/user.py:775
+#: users/models/user.py:785
 msgid "Need update password"
 msgstr "需要更新密码"
 
-#: users/models/user.py:913
+#: users/models/user.py:923
 msgid "Can invite user"
 msgstr "可以邀请用户"
 
-#: users/models/user.py:914
+#: users/models/user.py:924
 msgid "Can remove user"
 msgstr "可以移除用户"
 
-#: users/models/user.py:915
+#: users/models/user.py:925
 msgid "Can match user"
 msgstr "可以匹配用户"
 
-#: users/models/user.py:924
+#: users/models/user.py:934
 msgid "Administrator"
 msgstr "管理员"
 
-#: users/models/user.py:927
+#: users/models/user.py:937
 msgid "Administrator is the super user of system"
 msgstr "Administrator是初始的超级管理员"
 
-#: users/models/user.py:952
+#: users/models/user.py:962
 msgid "User password history"
 msgstr "用户密码历史"
 
@@ -6632,6 +6699,14 @@ msgstr "为了安全,仅列出几个用户"
 msgid "name not unique"
 msgstr "名称重复"
 
+#: users/signal_handlers.py:27
+msgid ""
+"The administrator has enabled \"Only allow existing users to log in\", \n"
+"            and the current user is not in the user list. Please contact the "
+"administrator."
+msgstr ""
+"管理员已开启'仅允许已存在用户登录',当前用户不在用户列表中,请联系管理员。"
+
 #: users/tasks.py:21
 msgid "Check password expired"
 msgstr "校验密码已过期"
@@ -7459,3 +7534,11 @@ msgstr "旗舰版"
 #: xpack/plugins/license/models.py:86
 msgid "Community edition"
 msgstr "社区版"
+
+#, fuzzy
+#~| msgid "Trigger mode"
+#~ msgid "Trigger type"
+#~ msgstr "触发模式"
+
+#~ msgid "Please login with a password and then bind the FeiShu"
+#~ msgstr "请使用密码登录,然后绑定飞书"
diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py
index 4bcb9be60..2045b6f29 100644
--- a/apps/ops/ansible/callback.py
+++ b/apps/ops/ansible/callback.py
@@ -1,4 +1,5 @@
 from collections import defaultdict
+from functools import reduce
 
 
 class DefaultCallback:
@@ -18,6 +19,7 @@ class DefaultCallback:
             failures=defaultdict(dict),
             dark=defaultdict(dict),
             skipped=defaultdict(dict),
+            ignored=defaultdict(dict),
         )
         self.summary = dict(
             ok=[],
@@ -59,6 +61,14 @@ class DefaultCallback:
         }
         self.result['ok'][host][task] = detail
 
+    def runner_on_skipped(self, event_data, host=None, task=None, **kwargs):
+        detail = {
+            'action': event_data.get('task_action', ''),
+            'res': {},
+            'rc': 0,
+        }
+        self.result['skipped'][host][task] = detail
+
     def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
         detail = {
             'action': event_data.get('task_action', ''),
@@ -67,15 +77,9 @@ class DefaultCallback:
             'stdout': res.get('stdout', ''),
             'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';')
         }
-        self.result['failures'][host][task] = detail
-
-    def runner_on_skipped(self, event_data, host=None, task=None, **kwargs):
-        detail = {
-            'action': event_data.get('task_action', ''),
-            'res': {},
-            'rc': 0,
-        }
-        self.result['skipped'][host][task] = detail
+        ignore_errors = event_data.get('ignore_errors', False)
+        error_key = 'ignored' if ignore_errors else 'failures'
+        self.result[error_key][host][task] = detail
 
     def runner_on_unreachable(self, event_data, host=None, task=None, res=None, **kwargs):
         detail = {
@@ -106,13 +110,18 @@ class DefaultCallback:
 
     def playbook_on_stats(self, event_data, **kwargs):
         failed = []
-        for i in ['dark', 'failures']:
-            for host, tasks in self.result[i].items():
+        error_func = lambda err, task_detail: err + f"{task_detail[0]}: {task_detail[1]['stderr']};"
+        for tp in ['dark', 'failures']:
+            for host, tasks in self.result[tp].items():
                 failed.append(host)
-                error = ''
-                for task, detail in tasks.items():
-                    error += f'{task}: {detail["stderr"]};'
-                self.summary[i][host] = error.strip(';')
+                error = reduce(error_func, tasks.items(), '').strip(';')
+                self.summary[tp][host] = error
+
+        for host, tasks in self.result.get('ignored', {}).items():
+            ignore_errors = reduce(error_func, tasks.items(), '').strip(';')
+            if host in failed:
+                self.summary['failures'][host] += {ignore_errors}
+
         self.summary['ok'] = list(set(self.result['ok'].keys()) - set(failed))
         self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(failed))
 
diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py
index fc124b210..80922859b 100644
--- a/apps/ops/ansible/inventory.py
+++ b/apps/ops/ansible/inventory.py
@@ -9,8 +9,11 @@ __all__ = ['JMSInventory']
 
 
 class JMSInventory:
-    def __init__(self, assets, account_policy='privileged_first',
-                 account_prefer='root,Administrator', host_callback=None, exclude_localhost=False):
+    def __init__(
+            self, assets, account_policy='privileged_first',
+            account_prefer='root,Administrator', host_callback=None,
+            exclude_localhost=False, task_type=None
+    ):
         """
         :param assets:
         :param account_prefer: account username name if not set use account_policy
@@ -22,6 +25,7 @@ class JMSInventory:
         self.host_callback = host_callback
         self.exclude_hosts = {}
         self.exclude_localhost = exclude_localhost
+        self.task_type = task_type
 
     @staticmethod
     def clean_assets(assets):
@@ -73,6 +77,7 @@ class JMSInventory:
         return var
 
     def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway):
+        from accounts.const import AutomationTypes
         if not account:
             host['error'] = _("No account available")
             return host
@@ -92,6 +97,12 @@ class JMSInventory:
                 host['ansible_become_password'] = su_from.secret
             else:
                 host['ansible_become_password'] = account.secret
+        elif platform.su_enabled and not su_from and \
+                self.task_type in (AutomationTypes.change_secret, AutomationTypes.push_account):
+            host.update(self.make_account_ansible_vars(account))
+            host['ansible_become'] = True
+            host['ansible_become_user'] = 'root'
+            host['ansible_become_password'] = account.secret
         else:
             host.update(self.make_account_ansible_vars(account))
 
diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py
index 72d970dee..d4540269c 100644
--- a/apps/ops/api/celery.py
+++ b/apps/ops/api/celery.py
@@ -4,19 +4,19 @@ import os
 import re
 
 from celery.result import AsyncResult
-from rest_framework import generics, viewsets, mixins, status
 from django.shortcuts import get_object_or_404
 from django.utils.translation import ugettext as _
 from django_celery_beat.models import PeriodicTask
+from rest_framework import generics, viewsets, mixins, status
 from rest_framework.response import Response
 
+from common.api import LogTailApi, CommonApiMixin
 from common.exceptions import JMSException
 from common.permissions import IsValidUser
-from common.api import LogTailApi, CommonApiMixin
 from ops.celery import app
-from ..models import CeleryTaskExecution, CeleryTask
-from ..celery.utils import get_celery_task_log_path
 from ..ansible.utils import get_ansible_task_log_path
+from ..celery.utils import get_celery_task_log_path
+from ..models import CeleryTaskExecution, CeleryTask
 from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer
 from ..serializers.celery import CeleryTaskSerializer, CeleryTaskExecutionSerializer
 
diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py
index 280ab5b98..6fac1bc95 100644
--- a/apps/ops/api/job.py
+++ b/apps/ops/api/job.py
@@ -1,23 +1,28 @@
 from django.conf import settings
 from django.db.models import Count
 from django.db.transaction import atomic
-from rest_framework.views import APIView
 from django.shortcuts import get_object_or_404
 from rest_framework.response import Response
+from rest_framework.views import APIView
 
+from assets.models import Asset
+from common.permissions import IsValidUser
 from ops.const import Types
 from ops.models import Job, JobExecution
 from ops.serializers.job import JobSerializer, JobExecutionSerializer
 
-__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView',
-           'JobAssetDetail', 'JobExecutionTaskDetail', 'FrequentUsernames']
+__all__ = [
+    'JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView',
+    'JobAssetDetail', 'JobExecutionTaskDetail', 'UsernameHintsAPI'
+]
 
 from ops.tasks import run_ops_job_execution
 from ops.variables import JMS_JOB_VARIABLE_HELP
 from orgs.mixins.api import OrgBulkModelViewSet
 from orgs.utils import tmp_to_org, get_current_org
 from accounts.models import Account
-from rbac.permissions import RBACPermission
+from perms.models import PermNode
+from perms.utils import UserPermAssetUtil
 
 
 def set_task_to_serializer_data(serializer, task):
@@ -26,9 +31,22 @@ def set_task_to_serializer_data(serializer, task):
     setattr(serializer, "_data", data)
 
 
+def merge_nodes_and_assets(nodes, assets, user):
+    if nodes:
+        perm_util = UserPermAssetUtil(user=user)
+        for node_id in nodes:
+            if node_id == PermNode.FAVORITE_NODE_KEY:
+                node_assets = perm_util.get_favorite_assets()
+            elif node_id == PermNode.UNGROUPED_NODE_KEY:
+                node_assets = perm_util.get_ungroup_assets()
+            else:
+                node, node_assets = perm_util.get_node_all_assets(node_id)
+            assets.extend(node_assets.exclude(id__in=[asset.id for asset in assets]))
+    return assets
+
+
 class JobViewSet(OrgBulkModelViewSet):
     serializer_class = JobSerializer
-    permission_classes = (RBACPermission,)
     search_fields = ('name', 'comment')
     model = Job
 
@@ -49,6 +67,10 @@ class JobViewSet(OrgBulkModelViewSet):
 
     def perform_create(self, serializer):
         run_after_save = serializer.validated_data.pop('run_after_save', False)
+        node_ids = serializer.validated_data.pop('nodes', [])
+        assets = serializer.validated_data.__getitem__('assets')
+        assets = merge_nodes_and_assets(node_ids, assets, self.request.user)
+        serializer.validated_data.__setitem__('assets', assets)
         instance = serializer.save()
         if instance.instant or run_after_save:
             self.run_job(instance, serializer)
@@ -70,9 +92,9 @@ class JobViewSet(OrgBulkModelViewSet):
 class JobExecutionViewSet(OrgBulkModelViewSet):
     serializer_class = JobExecutionSerializer
     http_method_names = ('get', 'post', 'head', 'options',)
-    permission_classes = (RBACPermission,)
     model = JobExecution
     search_fields = ('material',)
+    filterset_fields = ['status', 'job_id']
 
     @atomic
     def perform_create(self, serializer):
@@ -88,56 +110,66 @@ class JobExecutionViewSet(OrgBulkModelViewSet):
     def get_queryset(self):
         queryset = super().get_queryset()
         queryset = queryset.filter(creator=self.request.user)
-        job_id = self.request.query_params.get('job_id')
-        if job_id:
-            queryset = queryset.filter(job_id=job_id)
         return queryset
 
 
+class JobAssetDetail(APIView):
+    rbac_perms = {
+        'get': ['ops.view_jobexecution'],
+    }
+
+    def get(self, request, **kwargs):
+        execution_id = request.query_params.get('execution_id', '')
+        execution = get_object_or_404(JobExecution, id=execution_id)
+        return Response(data=execution.assent_result_detail)
+
+
+class JobExecutionTaskDetail(APIView):
+    rbac_perms = {
+        'GET': ['ops.view_jobexecution'],
+    }
+
+    def get(self, request, **kwargs):
+        org = get_current_org()
+        task_id = str(kwargs.get('task_id'))
+
+        with tmp_to_org(org):
+            execution = get_object_or_404(JobExecution, task_id=task_id)
+
+        return Response(data={
+            'status': execution.status,
+            'is_finished': execution.is_finished,
+            'is_success': execution.is_success,
+            'time_cost': execution.time_cost,
+            'job_id': execution.job.id,
+        })
+
+
 class JobRunVariableHelpAPIView(APIView):
-    rbac_perms = ()
-    permission_classes = ()
+    permission_classes = [IsValidUser]
 
     def get(self, request, **kwargs):
         return Response(data=JMS_JOB_VARIABLE_HELP)
 
 
-class JobAssetDetail(APIView):
-    rbac_perms = ()
-    permission_classes = ()
+class UsernameHintsAPI(APIView):
+    permission_classes = [IsValidUser]
 
-    def get(self, request, **kwargs):
-        execution_id = request.query_params.get('execution_id')
-        if execution_id:
-            execution = get_object_or_404(JobExecution, id=execution_id)
-            return Response(data=execution.assent_result_detail)
+    def post(self, request, **kwargs):
+        node_ids = request.data.get('nodes', None)
+        asset_ids = request.data.get('assets', [])
+        query = request.data.get('query', None)
 
+        assets = list(Asset.objects.filter(id__in=asset_ids).all())
 
-class JobExecutionTaskDetail(APIView):
-    rbac_perms = ()
-    permission_classes = ()
+        assets = merge_nodes_and_assets(node_ids, assets, request.user)
 
-    def get(self, request, **kwargs):
-        org = get_current_org()
-        task_id = str(kwargs.get('task_id'))
-        if task_id:
-            with tmp_to_org(org):
-                execution = get_object_or_404(JobExecution, task_id=task_id)
-                return Response(data={
-                    'status': execution.status,
-                    'is_finished': execution.is_finished,
-                    'is_success': execution.is_success,
-                    'time_cost': execution.time_cost,
-                    'job_id': execution.job.id,
-                })
-
-
-class FrequentUsernames(APIView):
-    rbac_perms = ()
-    permission_classes = ()
-
-    def get(self, request, **kwargs):
-        top_accounts = Account.objects.exclude(username='root').exclude(username__startswith='jms_').values(
-            'username').annotate(
-            total=Count('username')).order_by('total')[:5]
+        top_accounts = Account.objects \
+                           .exclude(username__startswith='jms_') \
+                           .exclude(username__startswith='js_') \
+                           .filter(username__icontains=query) \
+                           .filter(asset__in=assets) \
+                           .values('username') \
+                           .annotate(total=Count('username')) \
+                           .order_by('total', '-username')[:10]
         return Response(data=top_accounts)
diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py
index 657600e50..5166aa755 100644
--- a/apps/ops/serializers/celery.py
+++ b/apps/ops/serializers/celery.py
@@ -5,13 +5,14 @@ from django.utils.translation import gettext_lazy as _
 from django_celery_beat.models import PeriodicTask
 from rest_framework import serializers
 
+from ops.celery import app
+from ops.models import CeleryTask, CeleryTaskExecution
+
 __all__ = [
     'CeleryResultSerializer', 'CeleryTaskExecutionSerializer',
     'CeleryPeriodTaskSerializer', 'CeleryTaskSerializer'
 ]
 
-from ops.models import CeleryTask, CeleryTaskExecution
-
 
 class CeleryResultSerializer(serializers.Serializer):
     id = serializers.UUIDField()
@@ -37,11 +38,24 @@ class CeleryTaskSerializer(serializers.ModelSerializer):
 
 class CeleryTaskExecutionSerializer(serializers.ModelSerializer):
     is_success = serializers.BooleanField(required=False, read_only=True, label=_('Success'))
+    task_name = serializers.SerializerMethodField()
 
     class Meta:
         model = CeleryTaskExecution
         fields = [
-            "id", "name", "args", "kwargs", "time_cost", "timedelta",
+            "id", "name", "task_name", "args", "kwargs", "time_cost", "timedelta",
             "is_success", "is_finished", "date_published",
             "date_start", "date_finished"
         ]
+
+    @staticmethod
+    def get_task_name(obj):
+        from assets.const import AutomationTypes as AssetTypes
+        from accounts.const import AutomationTypes as AccountTypes
+        tp_dict = dict(AssetTypes.choices) | dict(AccountTypes.choices)
+        tp = obj.kwargs.get('tp')
+        task = app.tasks.get(obj.name)
+        task_name = getattr(task, 'verbose_name', obj.name)
+        if tp:
+            task_name = f'{task_name}({tp_dict.get(tp, tp)})'
+        return task_name
diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py
index 8b157b66f..011bea4e2 100644
--- a/apps/ops/serializers/job.py
+++ b/apps/ops/serializers/job.py
@@ -33,22 +33,6 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
         user = request.user if request else None
         return user
 
-    def create(self, validated_data):
-        assets = validated_data.__getitem__('assets')
-        node_ids = validated_data.pop('nodes', None)
-        if node_ids:
-            user = self.get_request_user()
-            perm_util = UserPermAssetUtil(user=user)
-            for node_id in node_ids:
-                if node_id == PermNode.FAVORITE_NODE_KEY:
-                    node_assets = perm_util.get_favorite_assets()
-                elif node_id == PermNode.UNGROUPED_NODE_KEY:
-                    node_assets = perm_util.get_ungroup_assets()
-                else:
-                    node, node_assets = perm_util.get_node_all_assets(node_id)
-                assets.extend(node_assets.exclude(id__in=[asset.id for asset in assets]))
-        return super().create(validated_data)
-
     class Meta:
         model = Job
         read_only_fields = [
diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html
index 2305218a9..3dfc23886 100644
--- a/apps/ops/templates/ops/celery_task_log.html
+++ b/apps/ops/templates/ops/celery_task_log.html
@@ -7,9 +7,12 @@
     
     
     
+    
+    
     
 
+
+    - 
+        ID:
+        
+    
 
+    - 
+        {% trans 'Task name' %}:
+        
+    
 
+    - 
+        {% trans 'Date start' %}:
+        
+    
 
+
 
 
 
@@ -52,6 +89,7 @@
     var failOverWsURL = scheme + "://" + document.location.hostname + ':' + failOverPort + url;
     var term;
     var ws;
+    var extraQuery = Object.fromEntries(new URLSearchParams(window.location.search));
 
     $(document).ready(function () {
         term = new Terminal({
@@ -85,7 +123,23 @@
                 term.write("Connect websocket server error")
             }
         }
+        getAutomationExecutionInfo();
     }).on('resize', window, function () {
         window.fit.fit(term);
     });
+    function getAutomationExecutionInfo() {
+        let url =  "{% url 'api-ops:task-executions-detail' pk=task_id %}";
+
+        requestApi({
+            url: url,
+            method: "GET",
+            flash_message: false,
+            success(data) {
+                const dateStart = new Date(data.date_start).toLocaleString();
+                $('.task-id').html(data.id);
+                $('.task-type').html(data.task_name);
+                $('.date-start').html(dateStart);
+            }
+        })
+    }
 
diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py
index 051ca7894..905f0ed0a 100644
--- a/apps/ops/urls/api_urls.py
+++ b/apps/ops/urls/api_urls.py
@@ -27,7 +27,7 @@ urlpatterns = [
     path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'),
     path('job-execution/asset-detail/', api.JobAssetDetail.as_view(), name='asset-detail'),
     path('job-execution/task-detail//', api.JobExecutionTaskDetail.as_view(), name='task-detail'),
-    path('frequent-username/', api.FrequentUsernames.as_view(), name='frequent-usernames'),
+    path('username-hints/', api.UsernameHintsAPI.as_view(), name='username-hints'),
     path('ansible/job-execution//log/', api.AnsibleTaskLogApi.as_view(), name='job-execution-log'),
 
     path('celery/task//task-execution//log/', api.CeleryTaskExecutionLogApi.as_view(),
diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py
index 01ce911c4..998ee216c 100644
--- a/apps/orgs/signal_handlers/common.py
+++ b/apps/orgs/signal_handlers/common.py
@@ -59,8 +59,6 @@ def expire_user_orgs(*args):
 
 @receiver(post_save, sender=Organization)
 def on_org_create(sender, instance, created=False, **kwargs):
-    if not created:
-        return
     expire_user_orgs()
 
 
@@ -80,7 +78,7 @@ def on_org_create_or_update(sender, instance, **kwargs):
 
 
 @receiver(pre_delete, sender=Organization)
-def on_org_delete(sender, instance, **kwargs):
+def delete_org_root_node_on_org_delete(sender, instance, **kwargs):
     expire_orgs_mapping_for_memory(instance.id)
 
     # 删除该组织下所有 节点
@@ -91,7 +89,7 @@ def on_org_delete(sender, instance, **kwargs):
 
 
 @receiver(post_delete, sender=Organization)
-def on_org_delete(sender, instance, **kwargs):
+def expire_user_orgs_on_org_delete(sender, instance, **kwargs):
     expire_user_orgs()
 
 
diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py
index 021933d40..5469c6ed3 100644
--- a/apps/perms/api/user_permission/tree/node_with_asset.py
+++ b/apps/perms/api/user_permission/tree/node_with_asset.py
@@ -1,4 +1,6 @@
 import abc
+import re
+from collections import defaultdict
 from urllib.parse import parse_qsl
 
 from django.conf import settings
@@ -11,6 +13,7 @@ from rest_framework.response import Response
 
 from accounts.const import AliasAccount
 from assets.api import SerializeToTreeNodeMixin
+from assets.const import AllTypes
 from assets.models import Asset
 from assets.utils import KubernetesTree
 from authentication.models import ConnectionToken
@@ -26,7 +29,8 @@ from ..mixin import SelfOrPKUserMixin
 __all__ = [
     'UserGrantedK8sAsTreeApi',
     'UserPermedNodesWithAssetsAsTreeApi',
-    'UserPermedNodeChildrenWithAssetsAsTreeApi'
+    'UserPermedNodeChildrenWithAssetsAsTreeApi',
+    'UserPermedNodeChildrenWithAssetsAsCategoryTreeApi',
 ]
 
 
@@ -137,6 +141,74 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi):
         return self.query_node_key or self.default_unfolded_node_key
 
 
+class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi(
+    SelfOrPKUserMixin, SerializeToTreeNodeMixin, ListAPIView
+):
+    @property
+    def is_sync(self):
+        sync = self.request.query_params.get('sync', 0)
+        return int(sync) == 1
+
+    @property
+    def tp(self):
+        return self.request.query_params.get('type')
+
+    def get_assets(self):
+        query_asset_util = UserPermAssetUtil(self.user)
+        node = PermNode.objects.filter(
+            granted_node_rels__user=self.user, parent_key='').first()
+        if node:
+            __, assets = query_asset_util.get_node_all_assets(node.id)
+        else:
+            assets = Asset.objects.none()
+        return assets
+
+    def to_tree_nodes(self, assets):
+        if not assets:
+            return []
+        assets = assets.annotate(tp=F('platform__type'))
+        asset_type_map = defaultdict(list)
+        for asset in assets:
+            asset_type_map[asset.tp].append(asset)
+        tp = self.tp
+        if tp:
+            assets = asset_type_map.get(tp, [])
+            if not assets:
+                return []
+            pid = f'ROOT_{str(assets[0].category).upper()}_{tp}'
+            return self.serialize_assets(assets, pid=pid)
+        resource_platforms = assets.order_by('id').values_list('platform_id', flat=True)
+        node_all = AllTypes.get_tree_nodes(resource_platforms)
+        pattern = re.compile(r'\(0\)?')
+        nodes = []
+        for node in node_all:
+            meta = node.get('meta', {})
+            if pattern.search(node['name']) or meta.get('type') == 'platform':
+                continue
+            _type = meta.get('_type')
+            if _type:
+                node['type'] = _type
+            nodes.append(node)
+
+        if not self.is_sync:
+            return nodes
+
+        asset_nodes = []
+        for node in nodes:
+            node['open'] = True
+            tp = node.get('meta', {}).get('_type')
+            if not tp:
+                continue
+            assets = asset_type_map.get(tp, [])
+            asset_nodes += self.serialize_assets(assets, pid=node['id'])
+        return nodes + asset_nodes
+
+    def list(self, request, *args, **kwargs):
+        assets = self.get_assets()
+        nodes = self.to_tree_nodes(assets)
+        return Response(data=nodes)
+
+
 class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView):
     """ 用户授权的K8s树 """
 
diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py
index 43876d864..4d037e4d3 100644
--- a/apps/perms/serializers/permission.py
+++ b/apps/perms/serializers/permission.py
@@ -97,6 +97,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
                 if condition in username_secret_type_dict:
                     continue
                 account_data = {key: getattr(template, key) for key in account_attribute}
+                account_data['su_from'] = template.get_su_from_account(asset)
                 account_data['name'] = f"{account_data['name']}-{_('Account template')}"
                 need_create_accounts.append(Account(**{'asset_id': asset.id, **account_data}))
         return Account.objects.bulk_create(need_create_accounts)
diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py
index 4c175cba5..7d054c0d7 100644
--- a/apps/perms/serializers/user_permission.py
+++ b/apps/perms/serializers/user_permission.py
@@ -8,7 +8,7 @@ from rest_framework import serializers
 from accounts.models import Account
 from assets.const import Category, AllTypes
 from assets.models import Node, Asset, Platform
-from assets.serializers.asset.common import AssetProtocolsPermsSerializer
+from assets.serializers.asset.common import AssetProtocolsPermsSerializer, AssetLabelSerializer
 from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
 from orgs.mixins.serializers import OrgResourceModelSerializerMixin
 from perms.serializers.permission import ActionChoicesField
@@ -25,13 +25,15 @@ class AssetPermedSerializer(OrgResourceModelSerializerMixin):
     protocols = AssetProtocolsPermsSerializer(many=True, required=False, label=_('Protocols'))
     category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
     type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
+    labels = AssetLabelSerializer(many=True, required=False, label=_('Label'))
     domain = ObjectRelatedField(required=False, queryset=Node.objects, label=_('Domain'))
 
     class Meta:
         model = Asset
         only_fields = [
-            "id", "name", "address", 'domain', 'platform',
-            "comment", "org_id", "is_active",
+            'id', 'name', 'address', 'domain', 'platform',
+            'comment', 'org_id', 'is_active', 'date_verified',
+            'created_by', 'date_created', 'connectivity', 'nodes', 'labels'
         ]
         fields = only_fields + ['protocols', 'category', 'type'] + ['org_name']
         read_only_fields = fields
diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py
index ba172747c..4f3d4c71d 100644
--- a/apps/perms/urls/user_permission.py
+++ b/apps/perms/urls/user_permission.py
@@ -37,6 +37,9 @@ user_permission_urlpatterns = [
     path('/nodes/children-with-assets/tree/',
          api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(),
          name='user-node-children-with-assets-as-tree'),
+    path('/nodes/children-with-assets/category/tree/',
+         api.UserPermedNodeChildrenWithAssetsAsCategoryTreeApi.as_view(),
+         name='user-node-children-with-assets-as-category-tree'),
     # 同步树
     path('/nodes/all-with-assets/tree/',
          api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
diff --git a/apps/perms/utils/user_perm.py b/apps/perms/utils/user_perm.py
index 12f979a4f..d054e67ce 100644
--- a/apps/perms/utils/user_perm.py
+++ b/apps/perms/utils/user_perm.py
@@ -1,15 +1,11 @@
-from assets.models import FavoriteAsset, Asset
-
 from django.conf import settings
 from django.db.models import Q
 
+from assets.models import FavoriteAsset, Asset
 from common.utils.common import timeit
-
 from perms.models import AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation
-
 from .permission import AssetPermissionUtil
 
-
 __all__ = ['AssetPermissionPermAssetUtil', 'UserPermAssetUtil', 'UserPermNodeUtil']
 
 
diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py
index f8f21d2bf..72b36e5dd 100644
--- a/apps/rbac/models/rolebinding.py
+++ b/apps/rbac/models/rolebinding.py
@@ -1,9 +1,9 @@
-from django.utils.translation import gettext_lazy as _
-from django.db import models
-from django.db.models import Q
 from django.conf import settings
 from django.core.exceptions import ValidationError
+from django.db import models
+from django.db.models import Q
 from django.db.models.signals import post_save
+from django.utils.translation import gettext_lazy as _
 from rest_framework.serializers import ValidationError
 
 from common.db.models import JMSBaseModel, CASCADE_SIGNAL_SKIP
@@ -109,6 +109,13 @@ class RoleBinding(JMSBaseModel):
     def is_scope_org(self):
         return self.scope == Scope.org
 
+    @staticmethod
+    def orgs_order_by_name(orgs):
+        from orgs.models import Organization
+        default_system_org_ids = [Organization.DEFAULT_ID, Organization.SYSTEM_ID]
+        default_system_orgs = orgs.filter(id__in=default_system_org_ids)
+        return default_system_orgs | orgs.exclude(id__in=default_system_org_ids).order_by('name')
+
     @classmethod
     def get_user_has_the_perm_orgs(cls, perm, user):
         from orgs.models import Organization
@@ -134,6 +141,7 @@ class RoleBinding(JMSBaseModel):
             org_ids = [b.org.id for b in bindings if b.org]
             orgs = all_orgs.filter(id__in=org_ids)
 
+        orgs = cls.orgs_order_by_name(orgs)
         workbench_perm = 'rbac.view_workbench'
         # 全局组织
         if orgs and perm != workbench_perm and user.has_perm('orgs.view_rootorg'):
@@ -183,7 +191,7 @@ class OrgRoleBinding(RoleBinding):
 
 class SystemRoleBindingManager(RoleBindingManager):
     def get_queryset(self):
-        queryset = super(RoleBindingManager, self).get_queryset()\
+        queryset = super(RoleBindingManager, self).get_queryset() \
             .filter(scope=Scope.system)
         return queryset
 
diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py
index e9861eb72..c195b5e4e 100644
--- a/apps/settings/serializers/public.py
+++ b/apps/settings/serializers/public.py
@@ -48,3 +48,4 @@ class PrivateSettingSerializer(PublicSettingSerializer):
     ANNOUNCEMENT = serializers.DictField()
 
     TICKETS_ENABLED = serializers.BooleanField()
+    CONNECTION_TOKEN_REUSABLE = serializers.BooleanField()
diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py
index 1dc41b037..891149add 100644
--- a/apps/settings/serializers/security.py
+++ b/apps/settings/serializers/security.py
@@ -94,15 +94,15 @@ class SecurityAuthSerializer(serializers.Serializer):
     )
     USER_LOGIN_SINGLE_MACHINE_ENABLED = serializers.BooleanField(
         required=False, default=False, label=_("Only single device login"),
-        help_text=_("Next device login, pre login will be logout")
+        help_text=_("After the user logs in on the new device, other logged-in devices will automatically log out")
     )
     ONLY_ALLOW_EXIST_USER_AUTH = serializers.BooleanField(
         required=False, default=False, label=_("Only exist user login"),
-        help_text=_("If enable, CAS、OIDC auth will be failed, if user not exist yet")
+        help_text=_("If enabled, non-existent users will not be allowed to log in; if disabled, users of other authentication methods except local authentication methods are allowed to log in and automatically create users (if the user does not exist)")
     )
     ONLY_ALLOW_AUTH_FROM_SOURCE = serializers.BooleanField(
         required=False, default=False, label=_("Only from source login"),
-        help_text=_("Only log in from the user source property")
+        help_text=_("If it is enabled, the user will only authenticate to the source when logging in; if it is disabled, the user will authenticate all the enabled authentication methods in a certain order when logging in, and as long as one of the authentication methods is successful, they can log in directly")
     )
     SECURITY_MFA_VERIFY_TTL = serializers.IntegerField(
         min_value=5, max_value=60 * 60 * 10,
diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py
index 89bc1f96c..cc40936e5 100644
--- a/apps/terminal/api/applet/host.py
+++ b/apps/terminal/api/applet/host.py
@@ -19,6 +19,9 @@ class AppletHostViewSet(JMSBulkModelViewSet):
     serializer_class = AppletHostSerializer
     queryset = AppletHost.objects.all()
     search_fields = ['asset_ptr__name', 'asset_ptr__address', ]
+    rbac_perms = {
+        'generate_accounts': 'terminal.change_applethost',
+    }
 
     def dispatch(self, request, *args, **kwargs):
         with tmp_to_builtin_org(system=1):
@@ -37,6 +40,12 @@ class AppletHostViewSet(JMSBulkModelViewSet):
         instance.check_terminal_binding(request)
         return Response({'msg': 'ok'})
 
+    @action(methods=['put'], detail=True, url_path='generate-accounts')
+    def generate_accounts(self, request, *args, **kwargs):
+        instance = self.get_object()
+        instance.generate_accounts()
+        return Response({'msg': 'ok'})
+
 
 class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
     serializer_class = AppletHostDeploymentSerializer
diff --git a/apps/terminal/api/session/session.py b/apps/terminal/api/session/session.py
index 13cd62b7a..0661536f7 100644
--- a/apps/terminal/api/session/session.py
+++ b/apps/terminal/api/session/session.py
@@ -186,6 +186,8 @@ class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet):
             tp = 'guacamole'
         if url.endswith('.cast.gz'):
             tp = 'asciicast'
+        if url.endswith('.replay.mp4'):
+            tp = 'mp4'
 
         download_url = reverse('api-terminal:session-replay-download', kwargs={'pk': session.id})
         data = {
diff --git a/apps/terminal/api/session/sharing.py b/apps/terminal/api/session/sharing.py
index a3d324205..9a60734fa 100644
--- a/apps/terminal/api/session/sharing.py
+++ b/apps/terminal/api/session/sharing.py
@@ -1,8 +1,8 @@
-from rest_framework.exceptions import MethodNotAllowed, ValidationError
-from rest_framework.decorators import action
-from rest_framework.response import Response
 from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
+from rest_framework.decorators import action
+from rest_framework.exceptions import MethodNotAllowed, ValidationError
+from rest_framework.response import Response
 
 from common.const.http import PATCH
 from orgs.mixins.api import OrgModelViewSet
diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml
index b68f6e06c..730f8e54f 100644
--- a/apps/terminal/automations/deploy_applet_host/playbook.yml
+++ b/apps/terminal/automations/deploy_applet_host/playbook.yml
@@ -25,6 +25,16 @@
       register: rds_install
 
     - name: Stop Tinker before install (jumpserver)
+      ansible.windows.win_powershell:
+        script: |
+          if (Get-Process -Name 'tinker' -ErrorAction SilentlyContinue) {
+            TASKKILL /F /IM tinker.exe /T
+          }
+          else {
+           $Ansible.Changed = $false
+          }
+
+    - name: Stop Tinkerd before install (jumpserver)
       ansible.windows.win_powershell:
         script: |
           if (Get-Service -Name 'JumpServer Tinker' -ErrorAction SilentlyContinue) {
diff --git a/apps/terminal/const.py b/apps/terminal/const.py
index fd0421427..cfec290c7 100644
--- a/apps/terminal/const.py
+++ b/apps/terminal/const.py
@@ -48,6 +48,7 @@ class TerminalType(TextChoices):
     magnus = 'magnus', 'Magnus'
     razor = 'razor', 'Razor'
     tinker = 'tinker', 'Tinker'
+    video_worker = 'video_worker', 'Video Worker'
 
     @classmethod
     def types(cls):
diff --git a/apps/terminal/migrations/0050_auto_20220606_1745.py b/apps/terminal/migrations/0050_auto_20220606_1745.py
index e88d37971..d0eb6ea5d 100644
--- a/apps/terminal/migrations/0050_auto_20220606_1745.py
+++ b/apps/terminal/migrations/0050_auto_20220606_1745.py
@@ -4,7 +4,6 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
         ('terminal', '0049_endpoint_redis_port'),
     ]
@@ -13,10 +12,10 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='terminal',
             name='type',
-            field=models.CharField(choices=[
-                ('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'),
-                ('xrdp', 'Xrdp'), ('lion', 'Lion'), ('core', 'Core'), ('celery', 'Celery'),
-                ('magnus', 'Magnus'), ('razor', 'Razor'), ('tinker', 'Tinker'),
-            ], default='koko', max_length=64, verbose_name='type'),
+            field=models.CharField(
+                choices=[('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'), ('xrdp', 'Xrdp'),
+                         ('lion', 'Lion'), ('core', 'Core'), ('celery', 'Celery'), ('magnus', 'Magnus'),
+                         ('razor', 'Razor'), ('tinker', 'Tinker'), ('video_worker', 'Video Worker')], default='koko',
+                max_length=64, verbose_name='type'),
         ),
     ]
diff --git a/apps/terminal/migrations/0061_applet_can_concurrent.py b/apps/terminal/migrations/0061_applet_can_concurrent.py
new file mode 100644
index 000000000..4ca762e65
--- /dev/null
+++ b/apps/terminal/migrations/0061_applet_can_concurrent.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.17 on 2023-05-09 11:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('terminal', '0060_sessionsharing_action_permission'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='applet',
+            name='can_concurrent',
+            field=models.BooleanField(default=True, verbose_name='Can concurrent'),
+        ),
+    ]
diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py
index c7296eda6..2114740ef 100644
--- a/apps/terminal/models/applet/applet.py
+++ b/apps/terminal/models/applet/applet.py
@@ -32,6 +32,7 @@ class Applet(JMSBaseModel):
     is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
     builtin = models.BooleanField(default=False, verbose_name=_('Builtin'))
     protocols = models.JSONField(default=list, verbose_name=_('Protocol'))
+    can_concurrent = models.BooleanField(default=True, verbose_name=_('Can concurrent'))
     tags = models.JSONField(default=list, verbose_name=_('Tags'))
     comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
     hosts = models.ManyToManyField(
@@ -134,37 +135,70 @@ class Applet(JMSBaseModel):
         shutil.copytree(path, pkg_path)
         return instance, serializer
 
-    def select_host_account(self):
-        # 选择激活的发布机
+    def select_host(self, user):
         hosts = [
             host for host in self.hosts.filter(is_active=True)
             if host.load != 'offline'
         ]
-
         if not hosts:
             return None
 
-        key_tmpl = 'applet_host_accounts_{}_{}'
-        host = random.choice(hosts)
-        using_keys = cache.keys(key_tmpl.format(host.id, '*')) or []
-        accounts_username_used = list(cache.get_many(using_keys).values())
-        logger.debug('Applet host account using: {}: {}'.format(host.name, accounts_username_used))
-        accounts = host.accounts.all() \
-            .filter(is_active=True, privileged=False) \
-            .exclude(username__in=accounts_username_used)
+        prefer_key = 'applet_host_prefer_{}'.format(user.id)
+        prefer_host_id = cache.get(prefer_key, None)
+        pref_host = [host for host in hosts if host.id == prefer_host_id]
+        if pref_host:
+            host = pref_host[0]
+        else:
+            host = random.choice(hosts)
+            cache.set(prefer_key, host.id, timeout=None)
+        return host
 
-        msg = 'Applet host remain accounts: {}: {}'.format(host.name, len(accounts))
+    @staticmethod
+    def random_select_prefer_account(user, host, accounts):
+        msg = 'Applet host remain public accounts: {}: {}'.format(host.name, len(accounts))
         if len(accounts) == 0:
             logger.error(msg)
-        else:
-            logger.debug(msg)
-
-        if not accounts:
             return None
+        prefer_host_account_key = 'applet_host_prefer_account_{}_{}'.format(user.id, host.id)
+        prefer_account_id = cache.get(prefer_host_account_key, None)
+        prefer_account = None
+        if prefer_account_id:
+            prefer_account = accounts.filter(id=prefer_account_id).first()
+        if prefer_account:
+            account = prefer_account
+        else:
+            account = random.choice(accounts)
+            cache.set(prefer_host_account_key, account.id, timeout=None)
+        return account
 
-        account = random.choice(accounts)
+    def select_host_account(self, user):
+        # 选择激活的发布机
+        host = self.select_host(user)
+        if not host:
+            return None
+        can_concurrent = self.can_concurrent and self.type == 'general'
+
+        accounts = host.accounts.all().filter(is_active=True, privileged=False)
+        private_account = accounts.filter(username='js_{}'.format(user.username)).first()
+        accounts_using_key_tmpl = 'applet_host_accounts_{}_{}'
+
+        if private_account and can_concurrent:
+            account = private_account
+        else:
+            using_keys = cache.keys(accounts_using_key_tmpl.format(host.id, '*')) or []
+            accounts_username_used = list(cache.get_many(using_keys).values())
+            logger.debug('Applet host account using: {}: {}'.format(host.name, accounts_username_used))
+
+            # 优先使用 private account
+            if private_account and private_account.username not in accounts_username_used:
+                account = private_account
+            else:
+                accounts = accounts.exclude(username__in=accounts_username_used).filter(username__startswith='jms_')
+                account = self.random_select_prefer_account(user, host, accounts)
+                if not account:
+                    return
         ttl = 60 * 60 * 24
-        lock_key = key_tmpl.format(host.id, account.username)
+        lock_key = accounts_using_key_tmpl.format(host.id, account.username)
         cache.set(lock_key, account.username, ttl)
 
         return {
diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py
index 47de718df..b0e7c478c 100644
--- a/apps/terminal/models/applet/host.py
+++ b/apps/terminal/models/applet/host.py
@@ -84,9 +84,13 @@ class AppletHost(Host):
         return random_string(16, special_char=True)
 
     def generate_accounts(self):
-        amount = int(os.getenv('TERMINAL_ACCOUNTS_AMOUNT', 100))
-        now_count = self.accounts.filter(privileged=False).count()
-        need = amount - now_count
+        self.generate_public_accounts()
+        self.generate_private_accounts()
+
+    def generate_public_accounts(self):
+        public_amount = int(os.getenv('TERMINAL_ACCOUNTS_AMOUNT', 100))
+        now_count = self.accounts.filter(privileged=False, username__startswith='jms').count()
+        need = public_amount - now_count
 
         accounts = []
         account_model = self.accounts.model
@@ -99,7 +103,31 @@ class AppletHost(Host):
                 org_id=self.LOCKING_ORG, is_active=False,
             )
             accounts.append(account)
-        bulk_create_with_history(accounts, account_model, batch_size=20)
+        bulk_create_with_history(accounts, account_model, batch_size=20, ignore_conflicts=True)
+
+    def generate_private_accounts_by_usernames(self, usernames):
+        accounts = []
+        account_model = self.accounts.model
+        for username in usernames:
+            password = self.random_password()
+            username = 'js_' + username
+            account = account_model(
+                username=username, secret=password, name=username,
+                asset_id=self.id, secret_type='password', version=1,
+                org_id=self.LOCKING_ORG, is_active=False,
+            )
+            accounts.append(account)
+        bulk_create_with_history(accounts, account_model, batch_size=20, ignore_conflicts=True)
+
+    def generate_private_accounts(self):
+        from users.models import User
+        usernames = User.objects \
+            .filter(is_active=True, is_service_account=False) \
+            .values_list('username', flat=True)
+        account_usernames = self.accounts.all().values_list('username', flat=True)
+        account_usernames = [username[3:] for username in account_usernames if username.startswith('js_')]
+        not_exist_users = set(usernames) - set(account_usernames)
+        self.generate_private_accounts_by_usernames(not_exist_users)
 
 
 class AppletHostDeployment(JMSBaseModel):
diff --git a/apps/terminal/models/session/session.py b/apps/terminal/models/session/session.py
index 2e0f3a5c9..5ddd6e647 100644
--- a/apps/terminal/models/session/session.py
+++ b/apps/terminal/models/session/session.py
@@ -48,8 +48,8 @@ class Session(OrgModelMixin):
 
     upload_to = 'replay'
     ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}'
-    SUFFIX_MAP = {1: '.gz', 2: '.replay.gz', 3: '.cast.gz'}
-    DEFAULT_SUFFIXES = ['.replay.gz', '.cast.gz', '.gz']
+    SUFFIX_MAP = {1: '.gz', 2: '.replay.gz', 3: '.cast.gz', 4: '.replay.mp4'}
+    DEFAULT_SUFFIXES = ['.replay.gz', '.cast.gz', '.gz', '.replay.mp4']
 
     # Todo: 将来干掉 local_path, 使用 default storage 实现
     def get_all_possible_local_path(self):
diff --git a/apps/terminal/models/session/sharing.py b/apps/terminal/models/session/sharing.py
index a62e23a85..c0e83dfda 100644
--- a/apps/terminal/models/session/sharing.py
+++ b/apps/terminal/models/session/sharing.py
@@ -133,6 +133,13 @@ class SessionJoinRecord(JMSBaseModel, OrgModelMixin):
         # self
         if self.verify_code != self.sharing.verify_code:
             return False, _('Invalid verification code')
+
+        # Link can only be joined once by the same user.
+        queryset = SessionJoinRecord.objects.filter(
+            verify_code=self.verify_code, sharing=self.sharing,
+            joiner=self.joiner, date_joined__lt=self.date_joined)
+        if queryset.exists():
+            return False, _('You have already joined this session')
         return True, ''
 
     def join_failed(self, reason):
diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py
index 572c1ecc3..67eea06be 100644
--- a/apps/terminal/serializers/session.py
+++ b/apps/terminal/serializers/session.py
@@ -61,7 +61,7 @@ class SessionDisplaySerializer(SessionSerializer):
 
 class ReplaySerializer(serializers.Serializer):
     file = serializers.FileField(allow_empty_file=True)
-    version = serializers.IntegerField(write_only=True, required=False, min_value=2, max_value=3)
+    version = serializers.IntegerField(write_only=True, required=False, min_value=2, max_value=4)
 
 
 class SessionJoinValidateSerializer(serializers.Serializer):
diff --git a/apps/terminal/signal_handlers/applet.py b/apps/terminal/signal_handlers/applet.py
index 18595fc7c..4fe390b8e 100644
--- a/apps/terminal/signal_handlers/applet.py
+++ b/apps/terminal/signal_handlers/applet.py
@@ -2,11 +2,14 @@ from django.db.models.signals import post_save, post_delete
 from django.dispatch import receiver
 from django.utils.functional import LazyObject
 
+from accounts.models import Account
 from common.signals import django_ready
 from common.utils import get_logger
 from common.utils.connection import RedisPubSub
 from orgs.utils import tmp_to_builtin_org
+from users.models import User
 from ..models import Applet, AppletHost
+from ..tasks import applet_host_generate_accounts
 from ..utils import DBPortManager
 
 db_port_manager: DBPortManager
@@ -19,12 +22,30 @@ def on_applet_host_create(sender, instance, created=False, **kwargs):
         return
     applets = Applet.objects.all()
     instance.applets.set(applets)
-    with tmp_to_builtin_org(system=1):
-        instance.generate_accounts()
 
+    applet_host_generate_accounts.delay(instance.id)
     applet_host_change_pub_sub.publish(True)
 
 
+@receiver(post_save, sender=User)
+def on_user_create_create_account(sender, instance, created=False, **kwargs):
+    if not created:
+        return
+
+    with tmp_to_builtin_org(system=1):
+        applet_hosts = AppletHost.objects.all()
+        for host in applet_hosts:
+            host.generate_private_accounts_by_usernames([instance.username])
+
+
+@receiver(post_delete, sender=User)
+def on_user_delete_remove_account(sender, instance, **kwargs):
+    with tmp_to_builtin_org(system=1):
+        applet_hosts = AppletHost.objects.all().values_list('id', flat=True)
+        accounts = Account.objects.filter(asset_id__in=applet_hosts, username=instance.username)
+        accounts.delete()
+
+
 @receiver(post_delete, sender=AppletHost)
 def on_applet_host_delete(sender, instance, **kwargs):
     applet_host_change_pub_sub.publish(True)
diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py
index 28356972f..66e6871d7 100644
--- a/apps/terminal/tasks.py
+++ b/apps/terminal/tasks.py
@@ -16,7 +16,7 @@ from ops.celery.decorator import (
 from orgs.utils import tmp_to_builtin_org
 from .backends import server_replay_storage
 from .models import (
-    Status, Session, Task, AppletHostDeployment
+    Status, Session, Task, AppletHostDeployment, AppletHost
 )
 from .utils import find_session_replay_local
 
@@ -82,7 +82,7 @@ def upload_session_replay_to_external_storage(session_id):
 
 @shared_task(
     verbose_name=_('Run applet host deployment'),
-    activity_callback=lambda self, did, *args, **kwargs: ([did], )
+    activity_callback=lambda self, did, *args, **kwargs: ([did],)
 )
 def run_applet_host_deployment(did):
     with tmp_to_builtin_org(system=1):
@@ -98,3 +98,16 @@ def run_applet_host_deployment_install_applet(did, applet_id):
     with tmp_to_builtin_org(system=1):
         deployment = AppletHostDeployment.objects.get(id=did)
         deployment.install_applet(applet_id)
+
+
+@shared_task(
+    verbose_name=_('Generate applet host accounts'),
+    activity_callback=lambda self, host_id, *args, **kwargs: ([host_id],)
+)
+def applet_host_generate_accounts(host_id):
+    applet_host = AppletHost.objects.filter(id=host_id).first()
+    if not applet_host:
+        return
+
+    with tmp_to_builtin_org(system=1):
+        applet_host.generate_accounts()
diff --git a/apps/terminal/utils/components.py b/apps/terminal/utils/components.py
index a3de26322..100391dbd 100644
--- a/apps/terminal/utils/components.py
+++ b/apps/terminal/utils/components.py
@@ -140,7 +140,7 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil):
             for component in self.components:
                 if not component.is_alive:
                     continue
-                component_stat = component.latest_stat
+                component_stat = component.last_stat
                 if not component_stat:
                     continue
                 metric_text = state_metric_text % (
diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py
index ac59ad13b..98587f266 100644
--- a/apps/tickets/serializers/ticket/apply_asset.py
+++ b/apps/tickets/serializers/ticket/apply_asset.py
@@ -40,6 +40,8 @@ class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer):
         ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
         extra_kwargs = {
             'apply_accounts': {'required': False},
+            'apply_date_start': {'allow_null': False},
+            'apply_date_expired': {'allow_null': False},
         }
         extra_kwargs.update(ticket_extra_kwargs)
 
diff --git a/apps/users/api/user.py b/apps/users/api/user.py
index c7882f6b2..7d964977c 100644
--- a/apps/users/api/user.py
+++ b/apps/users/api/user.py
@@ -12,13 +12,18 @@ from common.drf.filters import AttrRulesFilterBackend
 from common.utils import get_logger
 from orgs.utils import current_org, tmp_to_root_org
 from rbac.models import Role, RoleBinding
+from rbac.permissions import RBACPermission
 from users.utils import LoginBlockUtil, MFABlockUtils
 from .mixins import UserQuerysetMixin
 from .. import serializers
 from ..filters import UserFilter
 from ..models import User
 from ..notifications import ResetMFAMsg
-from ..serializers import UserSerializer, MiniUserSerializer, InviteSerializer
+from ..permissions import UserObjectPermission
+from ..serializers import (
+    UserSerializer,
+    MiniUserSerializer, InviteSerializer
+)
 from ..signals import post_user_create
 
 logger = get_logger(__name__)
@@ -32,6 +37,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, SuggestionMixin, BulkModelV
     filterset_class = UserFilter
     extra_filter_backends = [AttrRulesFilterBackend]
     search_fields = ('username', 'email', 'name')
+    permission_classes = [RBACPermission, UserObjectPermission]
     serializer_classes = {
         'default': UserSerializer,
         'suggestion': MiniUserSerializer,
diff --git a/apps/users/migrations/0040_alter_user_source.py b/apps/users/migrations/0040_alter_user_source.py
index 56ad9befc..cb90c4971 100644
--- a/apps/users/migrations/0040_alter_user_source.py
+++ b/apps/users/migrations/0040_alter_user_source.py
@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='user',
             name='source',
-            field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'), ('custom', 'Custom')], default='local', max_length=30, verbose_name='Source'),
+            field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'), ('wecom', 'WeCom'), ('dingtalk', 'DingTalk'), ('feishu', 'FeiShu'), ('custom', 'Custom')], default='local', max_length=30, verbose_name='Source'),
         ),
     ]
diff --git a/apps/users/models/user.py b/apps/users/models/user.py
index 5d1edb036..a156478bd 100644
--- a/apps/users/models/user.py
+++ b/apps/users/models/user.py
@@ -703,14 +703,15 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, JSONFilterMixin, Abstract
         cas = 'cas', 'CAS'
         saml2 = 'saml2', 'SAML2'
         oauth2 = 'oauth2', 'OAuth2'
+        wecom = 'wecom', _('WeCom')
+        dingtalk = 'dingtalk', _('DingTalk')
+        feishu = 'feishu', _('FeiShu')
         custom = 'custom', 'Custom'
 
     SOURCE_BACKEND_MAPPING = {
         Source.local: [
             settings.AUTH_BACKEND_MODEL,
             settings.AUTH_BACKEND_PUBKEY,
-            settings.AUTH_BACKEND_WECOM,
-            settings.AUTH_BACKEND_DINGTALK,
         ],
         Source.ldap: [
             settings.AUTH_BACKEND_LDAP
@@ -731,6 +732,15 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, JSONFilterMixin, Abstract
         Source.oauth2: [
             settings.AUTH_BACKEND_OAUTH2
         ],
+        Source.wecom: [
+            settings.AUTH_BACKEND_WECOM
+        ],
+        Source.feishu: [
+            settings.AUTH_BACKEND_FEISHU
+        ],
+        Source.dingtalk: [
+            settings.AUTH_BACKEND_DINGTALK
+        ],
         Source.custom: [
             settings.AUTH_BACKEND_CUSTOM
         ]
diff --git a/apps/users/permissions.py b/apps/users/permissions.py
index ee821f8d7..33081c5b3 100644
--- a/apps/users/permissions.py
+++ b/apps/users/permissions.py
@@ -1,5 +1,6 @@
 from rest_framework import permissions
 
+from rbac.builtin import BuiltinRole
 from .utils import is_auth_password_time_valid
 
 
@@ -7,4 +8,16 @@ class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
 
     def has_permission(self, request, view):
         return super().has_permission(request, view) \
-               and is_auth_password_time_valid(request.session)
+            and is_auth_password_time_valid(request.session)
+
+
+class UserObjectPermission(permissions.BasePermission):
+
+    def has_object_permission(self, request, view, obj):
+        if view.action not in ['update', 'partial_update', 'destroy']:
+            return True
+
+        if not request.user.is_superuser and obj.is_superuser:
+            return False
+
+        return True
diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py
index aa997d27f..eb8d35cd9 100644
--- a/apps/users/serializers/user.py
+++ b/apps/users/serializers/user.py
@@ -132,6 +132,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
             "last_login", "date_updated"  # 日期字段
         ]
         fields_bool = [
+            "is_superuser",
             "is_service_account", "is_valid",
             "is_expired", "is_active",  # 布尔字段
             "is_otp_secret_key_bound", "can_public_key_auth",
diff --git a/apps/users/signal_handlers.py b/apps/users/signal_handlers.py
index 487b3c917..2b2054ea3 100644
--- a/apps/users/signal_handlers.py
+++ b/apps/users/signal_handlers.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
 #
 from django.conf import settings
-from django.core.exceptions import PermissionDenied
 from django.db.models.signals import post_save
 from django.dispatch import receiver
+from django.utils.translation import ugettext_lazy as _
 from django_auth_ldap.backend import populate_user
 from django_cas_ng.signals import cas_user_authenticated
 
@@ -12,20 +12,33 @@ from authentication.backends.oidc.signals import openid_create_or_update_user
 from authentication.backends.saml2.signals import saml2_create_or_update_user
 from common.decorators import on_transaction_commit
 from common.utils import get_logger
+from jumpserver.utils import get_current_request
 from .models import User, UserPasswordHistory
 from .signals import post_user_create
 
 logger = get_logger(__file__)
 
 
-def user_authenticated_handle(user, created, source, attrs=None, **kwargs):
+def check_only_allow_exist_user_auth(created):
     if created and settings.ONLY_ALLOW_EXIST_USER_AUTH:
-        user.delete()
-        raise PermissionDenied(f'Not allow non-exist user auth: {user.username}')
+        request = get_current_request()
+        request.user_need_delete = True
+        request.error_message = _(
+            '''The administrator has enabled "Only allow existing users to log in", 
+            and the current user is not in the user list. Please contact the administrator.'''
+        )
+        return False
+    return True
+
+
+def user_authenticated_handle(user, created, source, attrs=None, **kwargs):
     if created:
         user.source = source
         user.save()
 
+    if not check_only_allow_exist_user_auth(created):
+        return
+
     if not attrs:
         return
 
@@ -122,10 +135,6 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
 
 @receiver(openid_create_or_update_user)
 def on_openid_create_or_update_user(sender, request, user, created, name, username, email, **kwargs):
-    if created and settings.ONLY_ALLOW_EXIST_USER_AUTH:
-        user.delete()
-        raise PermissionDenied(f'Not allow non-exist user auth: {username}')
-
     if created:
         logger.debug(
             "Receive OpenID user created signal: {}, "
@@ -133,7 +142,11 @@ def on_openid_create_or_update_user(sender, request, user, created, name, userna
         )
         user.source = User.Source.openid.value
         user.save()
-    elif not created and settings.AUTH_OPENID_ALWAYS_UPDATE_USER:
+
+    if not check_only_allow_exist_user_auth(created):
+        return
+
+    if not created and settings.AUTH_OPENID_ALWAYS_UPDATE_USER:
         logger.debug(
             "Receive OpenID user updated signal: {}, "
             "Update user info: {}"
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index e01baa999..a8ed07b01 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -1,6 +1,6 @@
 aiofiles==22.1.0
 amqp==5.0.9
-git+https://gitee.com/jumpserver/ansible@master#egg=ansible-core
+git+https://github.com/jumpserver/ansible@master#egg=ansible-core
 ansible==7.1.0
 ansible-runner==2.2.1
 asn1crypto==0.24.0
@@ -68,7 +68,7 @@ geoip2==4.5.0
 ipip-ipdb==1.6.1
 pywinrm==0.4.3
 # Django environment
-Django==3.2.17
+Django==3.2.19
 django-bootstrap3==14.2.0
 django-filter==2.4.0
 django-formtools==2.2
diff --git a/requirements/requirements_xpack.txt b/requirements/requirements_xpack.txt
index 4f0f0ea7d..c7d48e644 100644
--- a/requirements/requirements_xpack.txt
+++ b/requirements/requirements_xpack.txt
@@ -5,6 +5,7 @@ azure-identity==1.5.0
 azure-mgmt-compute==4.6.2
 azure-mgmt-network==2.7.0
 google-cloud-compute==0.5.0
+grpcio==1.54.2
 alibabacloud_dysmsapi20170525==2.0.2
 python-novaclient==11.0.1
 python-keystoneclient==4.3.0
diff --git a/utils/disable_user_mfa.sh b/utils/disable_user_mfa.sh
index ba98a9db8..0663c3643 100644
--- a/utils/disable_user_mfa.sh
+++ b/utils/disable_user_mfa.sh
@@ -16,7 +16,7 @@ user = User.objects.filter(username="${username}")
 if not user:
     print("No user found")
     sys.exit(1)
-user.update(otp_level=0)
+user.update(mfa_level=0)
 print("Disable user ${username} success")
 EOF
 }