From 41edeb902743c569d4232c59b230d98091f4536d Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 15 Mar 2023 19:43:11 +0800 Subject: [PATCH 001/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95=E5=88=B0syslog=E6=97=B6=E4=B8=AD?= =?UTF-8?q?=E6=96=87=E7=BC=96=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/encode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index 5a48261da..51b97b8b6 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -274,4 +274,4 @@ def ensure_last_char_is_ascii(data): def data_to_json(data, sort_keys=True, indent=2, cls=None): if cls is None: cls = DjangoJSONEncoder - return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls) + return json.dumps(data, ensure_ascii=False, sort_keys=sort_keys, indent=indent, cls=cls) From 8b6c2f4cc691bb59f811cbc2ac05e1dff1db7bb6 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 16 Mar 2023 09:56:25 +0800 Subject: [PATCH 002/100] =?UTF-8?q?workflow:=20=E4=BF=AE=E6=94=B9=20Gitee?= =?UTF-8?q?=20=E5=90=8C=E6=AD=A5=E7=9A=84=E7=9B=AE=E7=9A=84=E4=BB=93?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sync-gitee.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-gitee.yml b/.github/workflows/sync-gitee.yml index 55871042a..d4f5e6d68 100644 --- a/.github/workflows/sync-gitee.yml +++ b/.github/workflows/sync-gitee.yml @@ -20,4 +20,4 @@ jobs: SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }} with: source-repo: 'git@github.com:jumpserver/jumpserver.git' - destination-repo: 'git@gitee.com:jumpserver/jumpserver.git' + destination-repo: 'git@gitee.com:fit2cloud-feizhiyun/JumpServer.git' From 93a7cee4deedded06e24158dcecc293fab0a7097 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 16 Mar 2023 11:15:47 +0800 Subject: [PATCH 003/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E8=B4=A6=E5=8F=B7=E5=8E=86=E5=8F=B2=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/accounts/serializers/account/account.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index a06a11fd6..5b7bbdbce 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -135,8 +135,15 @@ class AccountHistorySerializer(serializers.ModelSerializer): class Meta: model = Account.history.model - fields = ['id', 'secret', 'secret_type', 'version', 'history_date', 'history_user'] + fields = [ + 'id', 'secret', 'secret_type', 'version', 'history_date', + 'history_user' + ] read_only_fields = fields + extra_kwargs = { + 'history_user': {'label': _('User')}, + 'history_date': {'label': _('Date')}, + } class AccountTaskSerializer(serializers.Serializer): From cad6fffd744c28ae84912d206f5f65ca6f75e473 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 16 Mar 2023 19:06:50 +0800 Subject: [PATCH 004/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20Ansible=20?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 43 ++++++++++++++++------------------- apps/ops/models/job.py | 2 +- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 2dbfc44ef..89442b34f 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -139,12 +139,12 @@ class JMSInventory: self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway) return host - def get_asset_accounts(self, asset): - from assets.const import Connectivity - accounts = asset.accounts.filter(is_active=True).order_by('-privileged', '-date_updated') - accounts_connectivity_ok = list(accounts.filter(connectivity=Connectivity.OK)) - accounts_connectivity_no = list(accounts.exclude(connectivity=Connectivity.OK)) - return accounts_connectivity_ok + accounts_connectivity_no + def get_asset_sorted_accounts(self, asset): + accounts = list(asset.accounts.filter(is_active=True)) + connectivity_score = {'ok': 2, '-': 1, 'err': 0} + sort_key = lambda x: (x.privileged, connectivity_score.get(x.connectivity, 0), x.date_updated) + accounts_sorted = sorted(accounts, key=sort_key, reverse=True) + return accounts_sorted @staticmethod def get_account_prefer(account_prefer): @@ -163,28 +163,23 @@ class JMSInventory: return account def select_account(self, asset): - accounts = self.get_asset_accounts(asset) - if not accounts or self.account_policy == 'skip': + accounts = self.get_asset_sorted_accounts(asset) + if not accounts: return None - account_selected = None - # 首先找到特权账号 - privileged_accounts = list(filter(lambda account: account.privileged, accounts)) + refer_account = self.get_refer_account(accounts) + if refer_account: + return refer_account - # 不同类型的账号选择,优先使用提供的名称 - refer_privileged_account = self.get_refer_account(privileged_accounts) - if self.account_policy in ['privileged_only', 'privileged_first']: - first_privileged = privileged_accounts[0] if privileged_accounts else None - account_selected = refer_privileged_account or first_privileged - - # 此策略不管是否匹配到账号都需强制返回 - if self.account_policy == 'privileged_only': + account_selected = accounts[0] + if self.account_policy == 'skip': + return None + elif self.account_policy == 'privileged_first': return account_selected - - if not account_selected: - account_selected = self.get_refer_account(accounts) - - return account_selected or accounts[0] + elif self.account_policy == 'privileged_only' and account_selected.privileged: + return account_selected + else: + return None def generate(self, path_dir): hosts = [] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index ed936ddba..3d631c06b 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -46,7 +46,7 @@ class JMSPermedInventory(JMSInventory): self.user = user self.assets_accounts_mapper = self.get_assets_accounts_mapper() - def get_asset_accounts(self, asset): + def get_asset_sorted_accounts(self, asset): return self.assets_accounts_mapper.get(asset.id, []) def get_assets_accounts_mapper(self): From c094bce71ee91ce0280cc805ba454826cde950b9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 17 Mar 2023 16:57:40 +0800 Subject: [PATCH 005/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20ansible=20?= =?UTF-8?q?=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 89442b34f..8c45cba39 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -49,12 +49,12 @@ class JMSInventory: if gateway.password: proxy_command_list.insert( - 0, "sshpass -p '{}'".format(gateway.password) + 0, "sshpass -p {}".format(gateway.password) ) if gateway.private_key: proxy_command_list.append("-i {}".format(gateway.private_key_path)) - proxy_command = '-o ProxyCommand=\"{}\"'.format( + proxy_command = "-o ProxyCommand='{}'".format( " ".join(proxy_command_list) ) return {"ansible_ssh_common_args": proxy_command} From 1acfdf0398ab0243e5a1dc27fe29141e93bba6ad Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 17 Mar 2023 17:10:10 +0800 Subject: [PATCH 006/100] =?UTF-8?q?perf:=20=E6=89=B9=E9=87=8F=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E8=B4=A6=E5=8F=B7=20=E5=88=86=E6=89=B9=E5=A4=84?= =?UTF-8?q?=E7=90=86=20(#10000)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/perms/serializers/permission.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index 27bc90d89..9260267cb 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # - -from django.db.models import Q, QuerySet +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers @@ -119,7 +118,10 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): return assets = self.get_all_assets(nodes, assets) accounts = self.create_accounts(assets) - push_accounts_to_assets_task.delay([str(account.id) for account in accounts]) + account_ids = [str(account.id) for account in accounts] + slice_count = 20 + for i in range(0, len(account_ids), slice_count): + push_accounts_to_assets_task.delay(account_ids[i:i + slice_count]) def validate_accounts(self, usernames: list[str]): template_ids = [] From 12db64ea1880aa4c52840d5891782ddce2bbad1c Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 17 Mar 2023 18:44:21 +0800 Subject: [PATCH 007/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/accounts/serializers/account/account.py | 2 +- apps/accounts/serializers/account/base.py | 2 +- apps/common/drf/parsers/base.py | 2 +- apps/locale/ja/LC_MESSAGES/django.po | 2 +- apps/locale/zh/LC_MESSAGES/django.po | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 5b7bbdbce..295c8ed1f 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -81,7 +81,7 @@ class AccountAssetSerializer(serializers.ModelSerializer): def to_internal_value(self, data): if isinstance(data, dict): - i = data.get('id') + i = data.get('id') or data.get('pk') else: i = data diff --git a/apps/accounts/serializers/account/base.py b/apps/accounts/serializers/account/base.py index f31f19196..88239c98e 100644 --- a/apps/accounts/serializers/account/base.py +++ b/apps/accounts/serializers/account/base.py @@ -16,7 +16,7 @@ class AuthValidateMixin(serializers.Serializer): choices=SecretType.choices, required=True, label=_('Secret type') ) secret = EncryptedField( - label=_('Secret/Password'), required=False, max_length=40960, allow_blank=True, + label=_('Secret'), required=False, max_length=40960, allow_blank=True, allow_null=True, write_only=True, ) passphrase = serializers.CharField( diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py index 7f3d4b055..94b9df159 100644 --- a/apps/common/drf/parsers/base.py +++ b/apps/common/drf/parsers/base.py @@ -111,7 +111,7 @@ class BaseFileParser(BaseParser): return {'pk': obj_id, 'name': obj_name} def parse_value(self, field, value): - if value is '-': + if value is '-' and field and field.allow_null: return None elif hasattr(field, 'to_file_internal_value'): value = field.to_file_internal_value(value) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index d4b4ecc20..8db56f83f 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -584,7 +584,7 @@ msgid "Asset type" msgstr "資産タイプ" #: accounts/serializers/account/base.py:19 -msgid "Secret/Password" +msgid "Secret" msgstr "キー/パスワード" #: accounts/serializers/account/base.py:24 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ba0a34c84..268aa8bdb 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -580,8 +580,8 @@ msgid "Asset type" msgstr "资产类型" #: accounts/serializers/account/base.py:19 -msgid "Secret/Password" -msgstr "密钥/密码" +msgid "Secret" +msgstr "密文" #: accounts/serializers/account/base.py:24 msgid "Key password" From f5523aaf7b1d4fa3c9a8a5b521644664007204f2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 17 Mar 2023 18:44:21 +0800 Subject: [PATCH 008/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/accounts/serializers/account/account.py | 2 +- apps/accounts/serializers/account/base.py | 2 +- apps/common/drf/parsers/base.py | 2 +- apps/locale/ja/LC_MESSAGES/django.po | 2 +- apps/locale/zh/LC_MESSAGES/django.po | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 5b7bbdbce..295c8ed1f 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -81,7 +81,7 @@ class AccountAssetSerializer(serializers.ModelSerializer): def to_internal_value(self, data): if isinstance(data, dict): - i = data.get('id') + i = data.get('id') or data.get('pk') else: i = data diff --git a/apps/accounts/serializers/account/base.py b/apps/accounts/serializers/account/base.py index f31f19196..88239c98e 100644 --- a/apps/accounts/serializers/account/base.py +++ b/apps/accounts/serializers/account/base.py @@ -16,7 +16,7 @@ class AuthValidateMixin(serializers.Serializer): choices=SecretType.choices, required=True, label=_('Secret type') ) secret = EncryptedField( - label=_('Secret/Password'), required=False, max_length=40960, allow_blank=True, + label=_('Secret'), required=False, max_length=40960, allow_blank=True, allow_null=True, write_only=True, ) passphrase = serializers.CharField( diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py index 7f3d4b055..94b9df159 100644 --- a/apps/common/drf/parsers/base.py +++ b/apps/common/drf/parsers/base.py @@ -111,7 +111,7 @@ class BaseFileParser(BaseParser): return {'pk': obj_id, 'name': obj_name} def parse_value(self, field, value): - if value is '-': + if value is '-' and field and field.allow_null: return None elif hasattr(field, 'to_file_internal_value'): value = field.to_file_internal_value(value) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index d4b4ecc20..8db56f83f 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -584,7 +584,7 @@ msgid "Asset type" msgstr "資産タイプ" #: accounts/serializers/account/base.py:19 -msgid "Secret/Password" +msgid "Secret" msgstr "キー/パスワード" #: accounts/serializers/account/base.py:24 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ba0a34c84..268aa8bdb 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -580,8 +580,8 @@ msgid "Asset type" msgstr "资产类型" #: accounts/serializers/account/base.py:19 -msgid "Secret/Password" -msgstr "密钥/密码" +msgid "Secret" +msgstr "密文" #: accounts/serializers/account/base.py:24 msgid "Key password" From 8e33c6f422b47c6dbc85f916913df5569128263e Mon Sep 17 00:00:00 2001 From: WeiZhixiong Date: Sat, 18 Mar 2023 23:19:51 +0800 Subject: [PATCH 009/100] fix: SyntaxWarning, apps/common/drf/parsers/base.py:114, "is" should be "==" --- apps/common/drf/parsers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py index 7f3d4b055..587da5251 100644 --- a/apps/common/drf/parsers/base.py +++ b/apps/common/drf/parsers/base.py @@ -111,7 +111,7 @@ class BaseFileParser(BaseParser): return {'pk': obj_id, 'name': obj_name} def parse_value(self, field, value): - if value is '-': + if value == '-': return None elif hasattr(field, 'to_file_internal_value'): value = field.to_file_internal_value(value) From 39e618c12760995175c456dbb6124921a6b15d06 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Sun, 19 Mar 2023 23:55:16 +0800 Subject: [PATCH 010/100] =?UTF-8?q?perf:=20=E8=B5=84=E4=BA=A7=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=9B=B4=E6=96=B0=E5=B9=B3=E5=8F=B0=20(#10013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/asset/asset.py | 32 ++++- apps/assets/notifications.py | 25 ++++ apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 207 ++++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 205 +++++++++++++------------- 6 files changed, 273 insertions(+), 204 deletions(-) create mode 100644 apps/assets/notifications.py diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index c917e09d9..1e887e572 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -2,15 +2,17 @@ # import django_filters from django.db.models import Q +from django.shortcuts import get_object_or_404 from django.utils.translation import gettext as _ from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.status import HTTP_200_OK from accounts.tasks import push_accounts_to_assets_task, verify_accounts_connectivity_task from assets import serializers from assets.exceptions import NotSupportedTemporarilyError from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend -from assets.models import Asset, Gateway +from assets.models import Asset, Gateway, Platform from assets.tasks import test_assets_connectivity_manual, update_assets_hardware_info_manual from common.api import SuggestionMixin from common.drf.filters import BaseFilterSet @@ -18,6 +20,7 @@ from common.utils import get_logger, is_uuid from orgs.mixins import generics from orgs.mixins.api import OrgBulkModelViewSet from ..mixin import NodeFilterMixin +from ...notifications import BulkUpdatePlatformSkipAssetUserMsg logger = get_logger(__file__) __all__ = [ @@ -109,6 +112,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): ("info", "assets.view_asset"), ) extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend] + skip_assets = [] def get_serializer_class(self): cls = super().get_serializer_class() @@ -144,6 +148,30 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): return Response({'error': error}, status=400) return super().create(request, *args, **kwargs) + def filter_bulk_update_data(self): + bulk_data = [] + for data in self.request.data: + pk = data.get('id') + platform = data.get('platform') + if not platform: + bulk_data.append(data) + continue + asset = get_object_or_404(Asset, pk=pk) + platform = get_object_or_404(Platform, **platform) + if platform.type == asset.type: + bulk_data.append(data) + continue + self.skip_assets.append(asset) + return bulk_data + def bulk_update(self, request, *args, **kwargs): + bulk_data = self.filter_bulk_update_data() + request._full_data = bulk_data + response = super().bulk_update(request, *args, **kwargs) + if response.status_code == HTTP_200_OK and self.skip_assets: + user = request.user + BulkUpdatePlatformSkipAssetUserMsg(user, self.skip_assets).publish() + return response + class AssetsTaskMixin: def perform_assets_task(self, serializer): @@ -155,7 +183,7 @@ class AssetsTaskMixin: else: asset = assets[0] if not asset.auto_info['ansible_enabled'] or \ - not asset.auto_info['ping_enabled']: + not asset.auto_info['ping_enabled']: raise NotSupportedTemporarilyError() task = test_assets_connectivity_manual(assets) return task diff --git a/apps/assets/notifications.py b/apps/assets/notifications.py new file mode 100644 index 000000000..14e84768e --- /dev/null +++ b/apps/assets/notifications.py @@ -0,0 +1,25 @@ +from django.utils.translation import ugettext as _ + +from notifications.notifications import UserMessage + + +class BulkUpdatePlatformSkipAssetUserMsg(UserMessage): + def __init__(self, user, assets): + super().__init__(user) + self.assets = assets + + def get_html_msg(self) -> dict: + subject = _("Batch update platform in assets, skipping assets that do not meet platform type") + message = f'
    {"".join([f"
  1. {asset}
  2. " for asset in self.assets])}
' + return { + 'subject': subject, + 'message': message + } + + @classmethod + def gen_test_msg(cls): + from users.models import User + from assets.models import Asset + user = User.objects.first() + assets = Asset.objects.all()[:10] + return cls(user, assets) \ No newline at end of file diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 472b329b3..64397eca5 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:6fa80b59b9b5f95a9cfcad8ec47eacd519bb962d139ab90463795a7b306a0a72 -size 137935 +oid sha256:03f75000def71efcfb08e7c1ee8122f6997b1f82be6cb747b52c33aa8b75a7bf +size 138164 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index d4b4ecc20..3abb73d5a 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-03-14 17:34+0800\n" +"POT-Creation-Date: 2023-03-19 23:52+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -169,7 +169,7 @@ msgstr "作成のみ" #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19 #: assets/serializers/label.py:27 audits/models.py:48 #: authentication/models/connection_token.py:33 -#: perms/models/asset_permission.py:64 perms/serializers/permission.py:35 +#: perms/models/asset_permission.py:64 perms/serializers/permission.py:34 #: terminal/backends/command/models.py:20 terminal/models/session/session.py:32 #: terminal/notifications.py:95 terminal/serializers/command.py:17 #: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:212 @@ -197,7 +197,7 @@ msgstr "ソース" #: acls/models/base.py:102 acls/serializers/base.py:57 #: assets/serializers/asset/common.py:131 assets/serializers/gateway.py:28 #: audits/models.py:49 ops/models/base.py:18 -#: perms/models/asset_permission.py:70 perms/serializers/permission.py:40 +#: perms/models/asset_permission.py:70 perms/serializers/permission.py:39 #: terminal/backends/command/models.py:21 terminal/models/session/session.py:34 #: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85 msgid "Account" @@ -447,7 +447,7 @@ msgstr "アカウントの確認" #: assets/models/group.py:20 assets/models/label.py:18 #: assets/models/platform.py:21 assets/models/platform.py:76 #: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:151 -#: assets/serializers/platform.py:133 +#: assets/serializers/platform.py:132 #: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 @@ -533,7 +533,7 @@ msgstr "エスクローされたパスワード" #: accounts/serializers/account/account.py:75 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:77 #: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:94 assets/serializers/platform.py:134 +#: assets/serializers/platform.py:93 assets/serializers/platform.py:133 #: perms/serializers/user_permission.py:26 settings/models.py:35 #: tickets/models/ticket/apply_application.py:13 msgid "Category" @@ -544,7 +544,7 @@ msgstr "カテゴリ" #: 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:78 -#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:93 +#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:92 #: audits/serializers.py:48 #: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 @@ -562,6 +562,30 @@ msgstr "タイプ" msgid "Asset not found" msgstr "資産が存在しません" +#: accounts/serializers/account/account.py:144 acls/models/base.py:98 +#: acls/models/login_acl.py:13 acls/serializers/base.py:55 +#: 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:29 +#: authentication/models/sso_token.py:16 +#: notifications/models/notification.py:12 +#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58 +#: perms/serializers/permission.py:30 rbac/builtin.py:122 +#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19 +#: 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/serializers/group.py:18 +msgid "User" +msgstr "ユーザー" + +#: accounts/serializers/account/account.py:145 +#: authentication/templates/authentication/_access_key_modal.html:33 +#: terminal/notifications.py:98 terminal/notifications.py:146 +msgid "Date" +msgstr "日付" + #: accounts/serializers/account/backup.py:31 #: accounts/serializers/automations/base.py:36 #: assets/serializers/automations/base.py:34 ops/mixin.py:23 ops/mixin.py:103 @@ -592,7 +616,7 @@ msgid "Key password" msgstr "キーパスワード" #: accounts/serializers/account/base.py:81 -#: assets/serializers/asset/common.py:301 +#: assets/serializers/asset/common.py:303 msgid "Spec info" msgstr "特別情報" @@ -733,24 +757,6 @@ msgstr "レビュー担当者" msgid "Active" msgstr "アクティブ" -#: acls/models/base.py:98 acls/models/login_acl.py:13 -#: acls/serializers/base.py:55 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:29 -#: authentication/models/sso_token.py:16 -#: notifications/models/notification.py:12 -#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58 -#: perms/serializers/permission.py:31 rbac/builtin.py:122 -#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19 -#: 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/serializers/group.py:18 -msgid "User" -msgstr "ユーザー" - #: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60 #: ops/serializers/job.py:65 terminal/const.py:67 #: terminal/models/session/session.py:43 terminal/serializers/command.py:18 @@ -764,7 +770,7 @@ msgid "Regex" msgstr "正規情報" #: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:30 msgid "Content" msgstr "コンテンツ" @@ -883,7 +889,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:61 +#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:62 msgid "IP" msgstr "IP" @@ -908,7 +914,7 @@ msgstr "アプリケーション" msgid "Can match application" msgstr "アプリケーションを一致させることができます" -#: assets/api/asset/asset.py:143 +#: assets/api/asset/asset.py:147 msgid "Cannot create asset directly, you should create a host or other" msgstr "" "資産を直接作成することはできません。ホストまたはその他を作成する必要がありま" @@ -1060,7 +1066,7 @@ msgid "Basic" msgstr "基本" #: assets/const/web.py:61 assets/models/asset/web.py:13 -#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:40 +#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:39 msgid "Script" msgstr "脚本" @@ -1196,7 +1202,7 @@ msgstr "アドレス" #: assets/models/asset/common.py:104 assets/models/platform.py:112 #: authentication/serializers/connect_token_secret.py:108 #: perms/serializers/user_permission.py:24 -#: xpack/plugins/cloud/serializers/account_attrs.py:187 +#: xpack/plugins/cloud/serializers/account_attrs.py:197 msgid "Platform" msgstr "プラットフォーム" @@ -1210,6 +1216,10 @@ msgstr "ドメイン" msgid "Labels" msgstr "ラベル" +#: assets/models/asset/common.py:111 assets/serializers/asset/host.py:27 +msgid "Info" +msgstr "情報" + #: assets/models/asset/common.py:283 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" @@ -1247,27 +1257,27 @@ msgstr "クライアントキー" msgid "Allow invalid cert" msgstr "証明書チェックを無視" -#: assets/models/asset/web.py:9 assets/serializers/platform.py:30 +#: assets/models/asset/web.py:9 assets/serializers/platform.py:29 msgid "Autofill" msgstr "自動充填" #: assets/models/asset/web.py:10 assets/serializers/asset/common.py:120 -#: assets/serializers/platform.py:32 +#: assets/serializers/platform.py:31 msgid "Username selector" msgstr "ユーザー名ピッカー" #: assets/models/asset/web.py:11 assets/serializers/asset/common.py:121 -#: assets/serializers/platform.py:35 +#: assets/serializers/platform.py:34 msgid "Password selector" msgstr "パスワードセレクター" #: assets/models/asset/web.py:12 assets/serializers/asset/common.py:122 -#: assets/serializers/platform.py:38 +#: assets/serializers/platform.py:37 msgid "Submit selector" msgstr "ボタンセレクターを確認する" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:300 rbac/tree.py:35 +#: assets/serializers/asset/common.py:302 rbac/tree.py:35 msgid "Accounts" msgstr "アカウント" @@ -1307,7 +1317,7 @@ msgid "Date verified" msgstr "確認済みの日付" #: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61 -#: perms/serializers/permission.py:33 users/models/group.py:25 +#: perms/serializers/permission.py:32 users/models/group.py:25 #: users/models/user.py:723 msgid "User group" msgstr "ユーザーグループ" @@ -1396,7 +1406,7 @@ msgstr "フルバリュー" msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:558 perms/serializers/permission.py:36 +#: assets/models/node.py:558 perms/serializers/permission.py:35 #: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96 msgid "Node" msgstr "ノード" @@ -1423,45 +1433,45 @@ msgstr "有効化" msgid "Ansible config" msgstr "Ansible 構成" -#: assets/models/platform.py:44 assets/serializers/platform.py:61 +#: assets/models/platform.py:44 assets/serializers/platform.py:60 msgid "Ping enabled" msgstr "アセット ディスカバリを有効にする" -#: assets/models/platform.py:45 assets/serializers/platform.py:62 +#: assets/models/platform.py:45 assets/serializers/platform.py:61 msgid "Ping method" msgstr "資産検出方法" #: assets/models/platform.py:46 assets/models/platform.py:59 -#: assets/serializers/platform.py:63 +#: assets/serializers/platform.py:62 msgid "Gather facts enabled" msgstr "資産情報の収集を有効にする" #: assets/models/platform.py:47 assets/models/platform.py:61 -#: assets/serializers/platform.py:64 +#: assets/serializers/platform.py:63 msgid "Gather facts method" msgstr "情報収集の方法" -#: assets/models/platform.py:48 assets/serializers/platform.py:67 +#: assets/models/platform.py:48 assets/serializers/platform.py:66 msgid "Change secret enabled" msgstr "パスワードの変更が有効" -#: assets/models/platform.py:50 assets/serializers/platform.py:68 +#: assets/models/platform.py:50 assets/serializers/platform.py:67 msgid "Change secret method" msgstr "パスワード変更モード" -#: assets/models/platform.py:52 assets/serializers/platform.py:69 +#: assets/models/platform.py:52 assets/serializers/platform.py:68 msgid "Push account enabled" msgstr "アカウントのプッシュを有効にする" -#: assets/models/platform.py:54 assets/serializers/platform.py:70 +#: assets/models/platform.py:54 assets/serializers/platform.py:69 msgid "Push account method" msgstr "アカウントプッシュ方式" -#: assets/models/platform.py:56 assets/serializers/platform.py:65 +#: assets/models/platform.py:56 assets/serializers/platform.py:64 msgid "Verify account enabled" msgstr "アカウントの確認をオンにする" -#: assets/models/platform.py:58 assets/serializers/platform.py:66 +#: assets/models/platform.py:58 assets/serializers/platform.py:65 msgid "Verify account method" msgstr "アカウント認証方法" @@ -1473,23 +1483,23 @@ msgstr "メタ" msgid "Internal" msgstr "ビルトイン" -#: assets/models/platform.py:83 assets/serializers/platform.py:91 +#: assets/models/platform.py:83 assets/serializers/platform.py:90 msgid "Charset" msgstr "シャーセット" -#: assets/models/platform.py:85 assets/serializers/platform.py:119 +#: assets/models/platform.py:85 assets/serializers/platform.py:118 msgid "Domain enabled" msgstr "ドメインを有効にする" -#: assets/models/platform.py:87 assets/serializers/platform.py:118 +#: assets/models/platform.py:87 assets/serializers/platform.py:117 msgid "Su enabled" msgstr "アカウントの切り替えを有効にする" -#: assets/models/platform.py:88 assets/serializers/platform.py:101 +#: assets/models/platform.py:88 assets/serializers/platform.py:100 msgid "Su method" msgstr "アカウントの切り替え方法" -#: assets/models/platform.py:90 assets/serializers/platform.py:98 +#: assets/models/platform.py:90 assets/serializers/platform.py:97 msgid "Automation" msgstr "オートメーション" @@ -1498,11 +1508,19 @@ msgstr "オートメーション" msgid "%(value)s is not an even number" msgstr "%(value)s は偶数ではありません" +#: assets/notifications.py:12 +msgid "" +"Batch update platform in assets, skipping assets that do not meet platform " +"type" +msgstr "" +"プラットフォームタイプがスキップされた資産に合致しない、資産内の一括更新プ" +"ラットフォーム" + #: assets/serializers/asset/common.py:119 msgid "Auto fill" msgstr "自動充填" -#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:96 +#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:95 #: authentication/serializers/connect_token_secret.py:28 #: authentication/serializers/connect_token_secret.py:66 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 @@ -1515,19 +1533,19 @@ msgid "Node path" msgstr "ノードパスです" #: assets/serializers/asset/common.py:150 -#: assets/serializers/asset/common.py:302 +#: assets/serializers/asset/common.py:304 msgid "Auto info" msgstr "自動情報" -#: assets/serializers/asset/common.py:226 +#: assets/serializers/asset/common.py:228 msgid "Platform not exist" msgstr "プラットフォームが存在しません" -#: assets/serializers/asset/common.py:261 +#: assets/serializers/asset/common.py:263 msgid "port out of range (1-65535)" msgstr "ポート番号が範囲外です (1-65535)" -#: assets/serializers/asset/common.py:268 +#: assets/serializers/asset/common.py:270 msgid "Protocol is required: {}" msgstr "プロトコルが必要です: {}" @@ -1588,10 +1606,6 @@ msgstr "システムバージョン" msgid "OS arch" msgstr "システムアーキテクチャ" -#: assets/serializers/asset/host.py:27 -msgid "Info" -msgstr "情報" - #: assets/serializers/cagegory.py:9 msgid "Constraints" msgstr "制約" @@ -1620,31 +1634,31 @@ msgstr "含まれない:/" msgid "The same level node name cannot be the same" msgstr "同じレベルのノード名を同じにすることはできません。" -#: assets/serializers/platform.py:26 +#: assets/serializers/platform.py:25 msgid "SFTP enabled" msgstr "SFTP が有効" -#: assets/serializers/platform.py:27 +#: assets/serializers/platform.py:26 msgid "SFTP home" msgstr "SFTP ルート パス" -#: assets/serializers/platform.py:43 +#: assets/serializers/platform.py:42 msgid "Auth with username" msgstr "ユーザー名で認証する" -#: assets/serializers/platform.py:71 +#: assets/serializers/platform.py:70 msgid "Gather accounts enabled" msgstr "アカウント収集を有効にする" -#: assets/serializers/platform.py:72 +#: assets/serializers/platform.py:71 msgid "Gather accounts method" msgstr "アカウントの収集方法" -#: assets/serializers/platform.py:78 +#: assets/serializers/platform.py:77 msgid "Primary" msgstr "主要" -#: assets/serializers/platform.py:120 +#: assets/serializers/platform.py:119 msgid "Default Domain" msgstr "デフォルト ドメイン" @@ -2494,15 +2508,15 @@ msgid "Ticket info" msgstr "作業指示情報" #: authentication/serializers/connection_token.py:20 -#: perms/models/asset_permission.py:71 perms/serializers/permission.py:37 -#: perms/serializers/permission.py:70 +#: perms/models/asset_permission.py:71 perms/serializers/permission.py:36 +#: perms/serializers/permission.py:69 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:18 msgid "Actions" msgstr "アクション" #: authentication/serializers/connection_token.py:41 -#: perms/serializers/permission.py:39 perms/serializers/permission.py:71 +#: perms/serializers/permission.py:38 perms/serializers/permission.py:70 #: users/serializers/user.py:93 users/serializers/user.py:165 msgid "Is expired" msgstr "期限切れです" @@ -2522,8 +2536,8 @@ msgstr "メール" msgid "The {} cannot be empty" msgstr "{} 空にしてはならない" -#: authentication/serializers/token.py:79 perms/serializers/permission.py:38 -#: perms/serializers/permission.py:72 users/serializers/user.py:94 +#: authentication/serializers/token.py:79 perms/serializers/permission.py:37 +#: perms/serializers/permission.py:71 users/serializers/user.py:94 #: users/serializers/user.py:163 msgid "Is valid" msgstr "有効です" @@ -2544,11 +2558,6 @@ msgstr "APIキー記号APIヘッダーを使用すると、すべてのリクエ msgid "docs" msgstr "ドキュメント" -#: authentication/templates/authentication/_access_key_modal.html:33 -#: terminal/notifications.py:98 terminal/notifications.py:146 -msgid "Date" -msgstr "日付" - #: authentication/templates/authentication/_access_key_modal.html:48 msgid "Show" msgstr "表示" @@ -2991,7 +3000,7 @@ msgstr "組織 ID" msgid "The file content overflowed (The maximum length `{}` bytes)" msgstr "ファイルの内容がオーバーフローしました (最大長 '{}' バイト)" -#: common/drf/parsers/base.py:189 +#: common/drf/parsers/base.py:193 msgid "Parse file error: {}" msgstr "解析ファイルエラー: {}" @@ -3242,11 +3251,11 @@ msgstr "投稿サイトニュース" msgid "No account available" msgstr "利用可能なアカウントがありません" -#: ops/ansible/inventory.py:189 +#: ops/ansible/inventory.py:196 msgid "Ansible disabled" msgstr "Ansible 無効" -#: ops/ansible/inventory.py:205 +#: ops/ansible/inventory.py:212 msgid "Skip hosts below:" msgstr "次のホストをスキップします: " @@ -4443,7 +4452,7 @@ msgid "SSO auth key TTL" msgstr "Token有効期間" #: settings/serializers/auth/sso.py:17 -#: xpack/plugins/cloud/serializers/account_attrs.py:184 +#: xpack/plugins/cloud/serializers/account_attrs.py:194 msgid "Unit: second" msgstr "単位: 秒" @@ -5547,7 +5556,7 @@ msgid "Redis port" msgstr "Redis ポート" #: terminal/models/component/endpoint.py:29 -#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:64 +#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:66 #: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50 #: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 #: terminal/serializers/storage.py:98 @@ -5811,15 +5820,15 @@ msgstr "アカウント" msgid "Timestamp" msgstr "タイムスタンプ" -#: terminal/serializers/endpoint.py:14 +#: terminal/serializers/endpoint.py:15 msgid "Oracle port" msgstr "Oracle ポート" -#: terminal/serializers/endpoint.py:17 +#: terminal/serializers/endpoint.py:18 msgid "Oracle port range" msgstr "Oracle がリッスンするポート範囲" -#: terminal/serializers/endpoint.py:19 +#: terminal/serializers/endpoint.py:20 msgid "" "Oracle proxy server listen port is dynamic, Each additional Oracle database " "instance adds a port listener" @@ -5827,13 +5836,13 @@ msgstr "" "Oracle プロキシサーバーがリッスンするポートは動的です。追加の Oracle データ" "ベースインスタンスはポートリスナーを追加します" -#: terminal/serializers/endpoint.py:35 +#: terminal/serializers/endpoint.py:36 msgid "Visit IP/Host, if empty, use the current request instead" msgstr "" "IP/ホストにアクセスします。空の場合は、代わりに現在のリクエストのアドレスを使" "用します" -#: terminal/serializers/endpoint.py:58 +#: terminal/serializers/endpoint.py:59 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "" @@ -7320,13 +7329,13 @@ msgstr "ファイルはJSON形式です。" msgid "IP address invalid `{}`, {}" msgstr "IPアドレスが無効: '{}', {}" -#: xpack/plugins/cloud/serializers/account_attrs.py:162 +#: xpack/plugins/cloud/serializers/account_attrs.py:172 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" msgstr "形式はコンマ区切りの文字列です,例:192.168.1.0/24,10.0.0.0-10.0.0.255" -#: xpack/plugins/cloud/serializers/account_attrs.py:166 +#: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "" "The port is used to detect the validity of the IP address. When the " "synchronization task is executed, only the valid IP address will be " @@ -7336,19 +7345,19 @@ msgstr "" "実行されると、有効な IP アドレスのみが同期されます。
ポートが0の場合、す" "べてのIPアドレスが有効です。" -#: xpack/plugins/cloud/serializers/account_attrs.py:174 +#: xpack/plugins/cloud/serializers/account_attrs.py:184 msgid "Hostname prefix" msgstr "ホスト名プレフィックス" -#: xpack/plugins/cloud/serializers/account_attrs.py:177 +#: xpack/plugins/cloud/serializers/account_attrs.py:187 msgid "IP segment" msgstr "IP セグメント" -#: xpack/plugins/cloud/serializers/account_attrs.py:181 +#: xpack/plugins/cloud/serializers/account_attrs.py:191 msgid "Test port" msgstr "テストポート" -#: xpack/plugins/cloud/serializers/account_attrs.py:184 +#: xpack/plugins/cloud/serializers/account_attrs.py:194 msgid "Test timeout" msgstr "テストタイムアウト" @@ -7430,22 +7439,22 @@ msgstr "ライセンスのインポートに成功" msgid "License is invalid" msgstr "ライセンスが無効です" -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:135 +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:138 msgid "License" msgstr "ライセンス" -#: xpack/plugins/license/models.py:79 +#: xpack/plugins/license/models.py:80 msgid "Standard edition" msgstr "標準版" -#: xpack/plugins/license/models.py:81 +#: xpack/plugins/license/models.py:82 msgid "Enterprise edition" msgstr "エンタープライズ版" -#: xpack/plugins/license/models.py:83 +#: xpack/plugins/license/models.py:84 msgid "Ultimate edition" msgstr "究極のエディション" -#: xpack/plugins/license/models.py:85 +#: xpack/plugins/license/models.py:86 msgid "Community edition" msgstr "コミュニティ版" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 360ae7c4b..176ff7f89 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:9819889a6d8b2934b06c5b242e3f63f404997f30851919247a405f542e8a03bc -size 113244 +oid sha256:fffd0032a105eb0c991f4eed7aca4fb344d9e8ab834f7fd2451b164532f198d7 +size 113407 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ba0a34c84..500e514c9 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-03-14 17:34+0800\n" +"POT-Creation-Date: 2023-03-19 23:52+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -168,7 +168,7 @@ msgstr "仅创建" #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19 #: assets/serializers/label.py:27 audits/models.py:48 #: authentication/models/connection_token.py:33 -#: perms/models/asset_permission.py:64 perms/serializers/permission.py:35 +#: perms/models/asset_permission.py:64 perms/serializers/permission.py:34 #: terminal/backends/command/models.py:20 terminal/models/session/session.py:32 #: terminal/notifications.py:95 terminal/serializers/command.py:17 #: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:212 @@ -196,7 +196,7 @@ msgstr "来源" #: acls/models/base.py:102 acls/serializers/base.py:57 #: assets/serializers/asset/common.py:131 assets/serializers/gateway.py:28 #: audits/models.py:49 ops/models/base.py:18 -#: perms/models/asset_permission.py:70 perms/serializers/permission.py:40 +#: perms/models/asset_permission.py:70 perms/serializers/permission.py:39 #: terminal/backends/command/models.py:21 terminal/models/session/session.py:34 #: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85 msgid "Account" @@ -446,7 +446,7 @@ msgstr "账号验证" #: assets/models/group.py:20 assets/models/label.py:18 #: assets/models/platform.py:21 assets/models/platform.py:76 #: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:151 -#: assets/serializers/platform.py:133 +#: assets/serializers/platform.py:132 #: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 @@ -529,7 +529,7 @@ msgstr "已托管密码" #: accounts/serializers/account/account.py:75 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:77 #: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:94 assets/serializers/platform.py:134 +#: assets/serializers/platform.py:93 assets/serializers/platform.py:133 #: perms/serializers/user_permission.py:26 settings/models.py:35 #: tickets/models/ticket/apply_application.py:13 msgid "Category" @@ -540,7 +540,7 @@ msgstr "类别" #: 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:78 -#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:93 +#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:92 #: audits/serializers.py:48 #: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 @@ -558,6 +558,30 @@ msgstr "类型" msgid "Asset not found" msgstr "资产不存在" +#: accounts/serializers/account/account.py:144 acls/models/base.py:98 +#: acls/models/login_acl.py:13 acls/serializers/base.py:55 +#: 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:29 +#: authentication/models/sso_token.py:16 +#: notifications/models/notification.py:12 +#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58 +#: perms/serializers/permission.py:30 rbac/builtin.py:122 +#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19 +#: 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/serializers/group.py:18 +msgid "User" +msgstr "用户" + +#: accounts/serializers/account/account.py:145 +#: authentication/templates/authentication/_access_key_modal.html:33 +#: terminal/notifications.py:98 terminal/notifications.py:146 +msgid "Date" +msgstr "日期" + #: accounts/serializers/account/backup.py:31 #: accounts/serializers/automations/base.py:36 #: assets/serializers/automations/base.py:34 ops/mixin.py:23 ops/mixin.py:103 @@ -588,7 +612,7 @@ msgid "Key password" msgstr "密钥密码" #: accounts/serializers/account/base.py:81 -#: assets/serializers/asset/common.py:301 +#: assets/serializers/asset/common.py:303 msgid "Spec info" msgstr "特殊信息" @@ -729,24 +753,6 @@ msgstr "审批人" msgid "Active" msgstr "激活中" -#: acls/models/base.py:98 acls/models/login_acl.py:13 -#: acls/serializers/base.py:55 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:29 -#: authentication/models/sso_token.py:16 -#: notifications/models/notification.py:12 -#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58 -#: perms/serializers/permission.py:31 rbac/builtin.py:122 -#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19 -#: 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/serializers/group.py:18 -msgid "User" -msgstr "用户" - #: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60 #: ops/serializers/job.py:65 terminal/const.py:67 #: terminal/models/session/session.py:43 terminal/serializers/command.py:18 @@ -760,7 +766,7 @@ msgid "Regex" msgstr "正则表达式" #: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:30 msgid "Content" msgstr "内容" @@ -877,7 +883,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:61 +#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:62 msgid "IP" msgstr "IP" @@ -902,7 +908,7 @@ msgstr "应用程序" msgid "Can match application" msgstr "匹配应用" -#: assets/api/asset/asset.py:143 +#: assets/api/asset/asset.py:147 msgid "Cannot create asset directly, you should create a host or other" msgstr "不能直接创建资产, 你应该创建主机或其他资产" @@ -1052,7 +1058,7 @@ msgid "Basic" msgstr "基本" #: assets/const/web.py:61 assets/models/asset/web.py:13 -#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:40 +#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:39 msgid "Script" msgstr "脚本" @@ -1188,7 +1194,7 @@ msgstr "地址" #: assets/models/asset/common.py:104 assets/models/platform.py:112 #: authentication/serializers/connect_token_secret.py:108 #: perms/serializers/user_permission.py:24 -#: xpack/plugins/cloud/serializers/account_attrs.py:187 +#: xpack/plugins/cloud/serializers/account_attrs.py:197 msgid "Platform" msgstr "系统平台" @@ -1202,6 +1208,10 @@ msgstr "网域" msgid "Labels" msgstr "标签管理" +#: assets/models/asset/common.py:111 assets/serializers/asset/host.py:27 +msgid "Info" +msgstr "信息" + #: assets/models/asset/common.py:283 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" @@ -1239,27 +1249,27 @@ msgstr "客户端密钥" msgid "Allow invalid cert" msgstr "忽略证书校验" -#: assets/models/asset/web.py:9 assets/serializers/platform.py:30 +#: assets/models/asset/web.py:9 assets/serializers/platform.py:29 msgid "Autofill" msgstr "自动代填" #: assets/models/asset/web.py:10 assets/serializers/asset/common.py:120 -#: assets/serializers/platform.py:32 +#: assets/serializers/platform.py:31 msgid "Username selector" msgstr "用户名选择器" #: assets/models/asset/web.py:11 assets/serializers/asset/common.py:121 -#: assets/serializers/platform.py:35 +#: assets/serializers/platform.py:34 msgid "Password selector" msgstr "密码选择器" #: assets/models/asset/web.py:12 assets/serializers/asset/common.py:122 -#: assets/serializers/platform.py:38 +#: assets/serializers/platform.py:37 msgid "Submit selector" msgstr "确认按钮选择器" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:300 rbac/tree.py:35 +#: assets/serializers/asset/common.py:302 rbac/tree.py:35 msgid "Accounts" msgstr "账号管理" @@ -1299,7 +1309,7 @@ msgid "Date verified" msgstr "校验日期" #: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61 -#: perms/serializers/permission.py:33 users/models/group.py:25 +#: perms/serializers/permission.py:32 users/models/group.py:25 #: users/models/user.py:723 msgid "User group" msgstr "用户组" @@ -1388,7 +1398,7 @@ msgstr "全称" msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:558 perms/serializers/permission.py:36 +#: assets/models/node.py:558 perms/serializers/permission.py:35 #: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96 msgid "Node" msgstr "节点" @@ -1415,45 +1425,45 @@ msgstr "启用" msgid "Ansible config" msgstr "Ansible 配置" -#: assets/models/platform.py:44 assets/serializers/platform.py:61 +#: assets/models/platform.py:44 assets/serializers/platform.py:60 msgid "Ping enabled" msgstr "启用资产探活" -#: assets/models/platform.py:45 assets/serializers/platform.py:62 +#: assets/models/platform.py:45 assets/serializers/platform.py:61 msgid "Ping method" msgstr "资产探活方式" #: assets/models/platform.py:46 assets/models/platform.py:59 -#: assets/serializers/platform.py:63 +#: assets/serializers/platform.py:62 msgid "Gather facts enabled" msgstr "启用收集资产信息" #: assets/models/platform.py:47 assets/models/platform.py:61 -#: assets/serializers/platform.py:64 +#: assets/serializers/platform.py:63 msgid "Gather facts method" msgstr "收集信息方式" -#: assets/models/platform.py:48 assets/serializers/platform.py:67 +#: assets/models/platform.py:48 assets/serializers/platform.py:66 msgid "Change secret enabled" msgstr "启用改密" -#: assets/models/platform.py:50 assets/serializers/platform.py:68 +#: assets/models/platform.py:50 assets/serializers/platform.py:67 msgid "Change secret method" msgstr "改密方式" -#: assets/models/platform.py:52 assets/serializers/platform.py:69 +#: assets/models/platform.py:52 assets/serializers/platform.py:68 msgid "Push account enabled" msgstr "启用账号推送" -#: assets/models/platform.py:54 assets/serializers/platform.py:70 +#: assets/models/platform.py:54 assets/serializers/platform.py:69 msgid "Push account method" msgstr "账号推送方式" -#: assets/models/platform.py:56 assets/serializers/platform.py:65 +#: assets/models/platform.py:56 assets/serializers/platform.py:64 msgid "Verify account enabled" msgstr "开启账号验证" -#: assets/models/platform.py:58 assets/serializers/platform.py:66 +#: assets/models/platform.py:58 assets/serializers/platform.py:65 msgid "Verify account method" msgstr "账号验证方式" @@ -1465,23 +1475,23 @@ msgstr "元数据" msgid "Internal" msgstr "内置" -#: assets/models/platform.py:83 assets/serializers/platform.py:91 +#: assets/models/platform.py:83 assets/serializers/platform.py:90 msgid "Charset" msgstr "编码" -#: assets/models/platform.py:85 assets/serializers/platform.py:119 +#: assets/models/platform.py:85 assets/serializers/platform.py:118 msgid "Domain enabled" msgstr "启用网域" -#: assets/models/platform.py:87 assets/serializers/platform.py:118 +#: assets/models/platform.py:87 assets/serializers/platform.py:117 msgid "Su enabled" msgstr "启用账号切换" -#: assets/models/platform.py:88 assets/serializers/platform.py:101 +#: assets/models/platform.py:88 assets/serializers/platform.py:100 msgid "Su method" msgstr "账号切换方式" -#: assets/models/platform.py:90 assets/serializers/platform.py:98 +#: assets/models/platform.py:90 assets/serializers/platform.py:97 msgid "Automation" msgstr "自动化" @@ -1490,11 +1500,17 @@ msgstr "自动化" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" +#: assets/notifications.py:12 +msgid "" +"Batch update platform in assets, skipping assets that do not meet platform " +"type" +msgstr "资产中批量更新平台,不符合平台类型跳过的资产" + #: assets/serializers/asset/common.py:119 msgid "Auto fill" msgstr "自动代填" -#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:96 +#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:95 #: authentication/serializers/connect_token_secret.py:28 #: authentication/serializers/connect_token_secret.py:66 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 @@ -1507,19 +1523,19 @@ msgid "Node path" msgstr "节点路径" #: assets/serializers/asset/common.py:150 -#: assets/serializers/asset/common.py:302 +#: assets/serializers/asset/common.py:304 msgid "Auto info" msgstr "自动化信息" -#: assets/serializers/asset/common.py:226 +#: assets/serializers/asset/common.py:228 msgid "Platform not exist" msgstr "平台不存在" -#: assets/serializers/asset/common.py:261 +#: assets/serializers/asset/common.py:263 msgid "port out of range (1-65535)" msgstr "端口超出范围 (1-65535)" -#: assets/serializers/asset/common.py:268 +#: assets/serializers/asset/common.py:270 msgid "Protocol is required: {}" msgstr "协议是必填的: {}" @@ -1580,10 +1596,6 @@ msgstr "系统版本" msgid "OS arch" msgstr "系统架构" -#: assets/serializers/asset/host.py:27 -msgid "Info" -msgstr "信息" - #: assets/serializers/cagegory.py:9 msgid "Constraints" msgstr "约束" @@ -1612,31 +1624,31 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/platform.py:26 +#: assets/serializers/platform.py:25 msgid "SFTP enabled" msgstr "SFTP 已启用" -#: assets/serializers/platform.py:27 +#: assets/serializers/platform.py:26 msgid "SFTP home" msgstr "SFTP 根路径" -#: assets/serializers/platform.py:43 +#: assets/serializers/platform.py:42 msgid "Auth with username" msgstr "使用用户名认证" -#: assets/serializers/platform.py:71 +#: assets/serializers/platform.py:70 msgid "Gather accounts enabled" msgstr "启用账号收集" -#: assets/serializers/platform.py:72 +#: assets/serializers/platform.py:71 msgid "Gather accounts method" msgstr "收集账号方式" -#: assets/serializers/platform.py:78 +#: assets/serializers/platform.py:77 msgid "Primary" msgstr "主要的" -#: assets/serializers/platform.py:120 +#: assets/serializers/platform.py:119 msgid "Default Domain" msgstr "默认网域" @@ -2472,15 +2484,15 @@ msgid "Ticket info" msgstr "工单信息" #: authentication/serializers/connection_token.py:20 -#: perms/models/asset_permission.py:71 perms/serializers/permission.py:37 -#: perms/serializers/permission.py:70 +#: perms/models/asset_permission.py:71 perms/serializers/permission.py:36 +#: perms/serializers/permission.py:69 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:18 msgid "Actions" msgstr "动作" #: authentication/serializers/connection_token.py:41 -#: perms/serializers/permission.py:39 perms/serializers/permission.py:71 +#: perms/serializers/permission.py:38 perms/serializers/permission.py:70 #: users/serializers/user.py:93 users/serializers/user.py:165 msgid "Is expired" msgstr "已过期" @@ -2500,8 +2512,8 @@ msgstr "邮箱" msgid "The {} cannot be empty" msgstr "{} 不能为空" -#: authentication/serializers/token.py:79 perms/serializers/permission.py:38 -#: perms/serializers/permission.py:72 users/serializers/user.py:94 +#: authentication/serializers/token.py:79 perms/serializers/permission.py:37 +#: perms/serializers/permission.py:71 users/serializers/user.py:94 #: users/serializers/user.py:163 msgid "Is valid" msgstr "是否有效" @@ -2522,11 +2534,6 @@ msgstr "使用api key签名请求头,每个请求的头部是不一样的" msgid "docs" msgstr "文档" -#: authentication/templates/authentication/_access_key_modal.html:33 -#: terminal/notifications.py:98 terminal/notifications.py:146 -msgid "Date" -msgstr "日期" - #: authentication/templates/authentication/_access_key_modal.html:48 msgid "Show" msgstr "显示" @@ -2961,7 +2968,7 @@ msgstr "组织 ID" msgid "The file content overflowed (The maximum length `{}` bytes)" msgstr "文件内容太大 (最大长度 `{}` 字节)" -#: common/drf/parsers/base.py:189 +#: common/drf/parsers/base.py:193 msgid "Parse file error: {}" msgstr "解析文件错误: {}" @@ -3207,11 +3214,11 @@ msgstr "发布站内消息" msgid "No account available" msgstr "无可用账号" -#: ops/ansible/inventory.py:189 +#: ops/ansible/inventory.py:196 msgid "Ansible disabled" msgstr "Ansible 已禁用" -#: ops/ansible/inventory.py:205 +#: ops/ansible/inventory.py:212 msgid "Skip hosts below:" msgstr "跳过以下主机: " @@ -4404,7 +4411,7 @@ msgid "SSO auth key TTL" msgstr "令牌有效期" #: settings/serializers/auth/sso.py:17 -#: xpack/plugins/cloud/serializers/account_attrs.py:184 +#: xpack/plugins/cloud/serializers/account_attrs.py:194 msgid "Unit: second" msgstr "单位: 秒" @@ -5475,7 +5482,7 @@ msgid "Redis port" msgstr "Redis 端口" #: terminal/models/component/endpoint.py:29 -#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:64 +#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:66 #: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50 #: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 #: terminal/serializers/storage.py:98 @@ -5737,15 +5744,15 @@ msgstr "账号" msgid "Timestamp" msgstr "时间戳" -#: terminal/serializers/endpoint.py:14 +#: terminal/serializers/endpoint.py:15 msgid "Oracle port" msgstr "Oracle 端口" -#: terminal/serializers/endpoint.py:17 +#: terminal/serializers/endpoint.py:18 msgid "Oracle port range" msgstr "Oracle 端口范围" -#: terminal/serializers/endpoint.py:19 +#: terminal/serializers/endpoint.py:20 msgid "" "Oracle proxy server listen port is dynamic, Each additional Oracle database " "instance adds a port listener" @@ -5753,11 +5760,11 @@ msgstr "" "Oracle 代理服务器监听端口是动态的,每增加一个 Oracle 数据库实例,就会增加一个" "端口监听" -#: terminal/serializers/endpoint.py:35 +#: terminal/serializers/endpoint.py:36 msgid "Visit IP/Host, if empty, use the current request instead" msgstr "访问IP/Host,如果为空,则使用当前请求的地址代替" -#: terminal/serializers/endpoint.py:58 +#: terminal/serializers/endpoint.py:59 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" @@ -7224,13 +7231,13 @@ msgstr "JSON 格式的文件" msgid "IP address invalid `{}`, {}" msgstr "IP 地址无效: `{}`, {}" -#: xpack/plugins/cloud/serializers/account_attrs.py:162 +#: xpack/plugins/cloud/serializers/account_attrs.py:172 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" msgstr "格式为逗号分隔的字符串,如:192.168.1.0/24,10.0.0.0-10.0.0.255" -#: xpack/plugins/cloud/serializers/account_attrs.py:166 +#: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "" "The port is used to detect the validity of the IP address. When the " "synchronization task is executed, only the valid IP address will be " @@ -7239,19 +7246,19 @@ msgstr "" "端口用来检测 IP 地址的有效性,在同步任务执行时,只会同步有效的 IP 地址。
" "如果端口为 0,则表示所有 IP 地址均有效。" -#: xpack/plugins/cloud/serializers/account_attrs.py:174 +#: xpack/plugins/cloud/serializers/account_attrs.py:184 msgid "Hostname prefix" msgstr "主机名前缀" -#: xpack/plugins/cloud/serializers/account_attrs.py:177 +#: xpack/plugins/cloud/serializers/account_attrs.py:187 msgid "IP segment" msgstr "IP 网段" -#: xpack/plugins/cloud/serializers/account_attrs.py:181 +#: xpack/plugins/cloud/serializers/account_attrs.py:191 msgid "Test port" msgstr "测试端口" -#: xpack/plugins/cloud/serializers/account_attrs.py:184 +#: xpack/plugins/cloud/serializers/account_attrs.py:194 msgid "Test timeout" msgstr "测试超时时间" @@ -7331,23 +7338,23 @@ msgstr "许可证导入成功" msgid "License is invalid" msgstr "无效的许可证" -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:135 +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:138 msgid "License" msgstr "许可证" -#: xpack/plugins/license/models.py:79 +#: xpack/plugins/license/models.py:80 msgid "Standard edition" msgstr "标准版" -#: xpack/plugins/license/models.py:81 +#: xpack/plugins/license/models.py:82 msgid "Enterprise edition" msgstr "企业版" -#: xpack/plugins/license/models.py:83 +#: xpack/plugins/license/models.py:84 msgid "Ultimate edition" msgstr "旗舰版" -#: xpack/plugins/license/models.py:85 +#: xpack/plugins/license/models.py:86 msgid "Community edition" msgstr "社区版" From eac4b41783ae81e9d136745339c3ec518bda9692 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 20 Mar 2023 09:59:34 +0800 Subject: [PATCH 011/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/parsers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py index 94b9df159..9ba77ca72 100644 --- a/apps/common/drf/parsers/base.py +++ b/apps/common/drf/parsers/base.py @@ -111,7 +111,7 @@ class BaseFileParser(BaseParser): return {'pk': obj_id, 'name': obj_name} def parse_value(self, field, value): - if value is '-' and field and field.allow_null: + if value == '-' and field and field.allow_null: return None elif hasattr(field, 'to_file_internal_value'): value = field.to_file_internal_value(value) From b6ccc53a71206d0580448cbb765df7a438065ccc Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 20 Mar 2023 10:02:50 +0800 Subject: [PATCH 012/100] =?UTF-8?q?perf:=20DBeaver=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E6=A3=80=E6=9F=A5=E6=9B=B4=E6=96=B0=E3=80=81?= =?UTF-8?q?=E4=B8=8D=E6=8F=90=E7=A4=BA=E5=88=9B=E5=BB=BA=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E3=80=81=E4=B8=8D=E5=BC=B9=E5=87=BA?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E9=A9=B1=E5=8A=A8=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/applets/dbeaver/app.py | 79 +++++++++++++++++- .../applets/dbeaver/config/drivers.xml | 83 +++++++++++++++++++ 2 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 apps/terminal/applets/dbeaver/config/drivers.xml diff --git a/apps/terminal/applets/dbeaver/app.py b/apps/terminal/applets/dbeaver/app.py index f99fdd54c..39d953926 100644 --- a/apps/terminal/applets/dbeaver/app.py +++ b/apps/terminal/applets/dbeaver/app.py @@ -4,6 +4,9 @@ import win32api import shutil import subprocess +from xml.etree import ElementTree +from xml.sax import SAXException + from common import wait_pid, BaseApplication @@ -21,16 +24,88 @@ class AppletApplication(BaseApplication): self.port = self.asset.get_protocol_port(self.protocol) self.db = self.asset.spec_info.db_name self.name = '%s-%s-%s' % (self.host, self.db, int(time.time())) + self.app_work_path = self.get_app_work_path() self.pid = None self.app = None - def launch(self): + @staticmethod + def get_app_work_path(): win_user_name = win32api.GetUserName() + return r'C:\Users\%s\AppData\Roaming\DBeaverData' % win_user_name + + @staticmethod + def _read_config(config_file): + default_config = {} + if not os.path.exists(config_file): + return default_config + + with open(config_file, 'r') as f: + for line in f.readlines(): + try: + config_key, config_value = line.split('=') + except ValueError: + continue + default_config[config_key] = config_value + return default_config + + @staticmethod + def _write_config(config_file, config): + with open(config_file, 'w')as f: + for key, value in config.items(): + f.write(f'{key}={value}\n') + + @staticmethod + def _merge_driver_xml(src_path, dest_path): + tree1 = ElementTree.parse(dest_path) + tree2 = ElementTree.parse(src_path) + + for child2 in tree2.getroot(): + found = False + for child1 in tree1.getroot(): + if child1.tag == child2.tag and child1.attrib == child2.attrib: + found = True + break + if not found: + tree1.getroot().append(child2) + tree1.write(dest_path) + + def init_driver(self): src_driver = os.path.join(os.path.dirname(self.path), 'drivers') - dest_driver = r'C:\Users\%s\AppData\Roaming\DBeaverData\drivers' % win_user_name + dest_driver = os.path.join(self.app_work_path, 'drivers') if not os.path.exists(dest_driver): shutil.copytree(src_driver, dest_driver, dirs_exist_ok=True) + def init_driver_config(self): + driver_yml_path = os.path.join( + self.app_work_path, 'workspace6', '.metadata', '.config', + ) + driver_yml_file = os.path.join(driver_yml_path, 'drivers.xml') + try: + self._merge_driver_xml('./config/drivers.xml', driver_yml_file) + except (SAXException, FileNotFoundError): + os.makedirs(driver_yml_path, exist_ok=True) + shutil.copy('./config/drivers.xml', driver_yml_file) + + def init_other_config(self): + config_path = os.path.join( + self.app_work_path, 'workspace6', '.metadata', + '.plugins', 'org.eclipse.core.runtime', '.settings', + ) + os.makedirs(config_path, exist_ok=True) + config_file = os.path.join(config_path, 'org.jkiss.dbeaver.core.prefs') + + config = self._read_config(config_file) + config['ui.auto.update.check'] = 'false' + config['sample.database.canceled'] = 'true' + config['tipOfTheDayInitializer.notFirstRun'] = 'true' + config['ui.show.tip.of.the.day.on.startup'] = 'false' + self._write_config(config_file, config) + + def launch(self): + self.init_driver() + self.init_driver_config() + self.init_other_config() + def _get_exec_params(self): driver = getattr(self, 'driver', self.protocol) params_string = f'name={self.name}|' \ diff --git a/apps/terminal/applets/dbeaver/config/drivers.xml b/apps/terminal/applets/dbeaver/config/drivers.xml new file mode 100644 index 000000000..2ce625a5b --- /dev/null +++ b/apps/terminal/applets/dbeaver/config/drivers.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 664ab0797a89802a4f3240944320b71099db6693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Mon, 20 Mar 2023 14:22:33 +0800 Subject: [PATCH 013/100] =?UTF-8?q?perf:=20=E4=BD=BF=E7=94=A8=20ghcr.io=20?= =?UTF-8?q?=E6=89=98=E7=AE=A1=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 2 +- .gitignore | 1 + Dockerfile-ee | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index a504fb4ec..81c9033ba 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,4 @@ celerybeat.pid ### Vagrant ### .vagrant/ apps/xpack/.git - +.history/ diff --git a/.gitignore b/.gitignore index 316630d9f..9573a70b7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ releashe /apps/script.py data/* test.py +.history/ diff --git a/Dockerfile-ee b/Dockerfile-ee index 63c65d21c..dc2ae308e 100644 --- a/Dockerfile-ee +++ b/Dockerfile-ee @@ -1,6 +1,6 @@ ARG VERSION FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack -FROM jumpserver/core:${VERSION} +FROM ghcr.io/jumpserver/core:${VERSION} COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack WORKDIR /opt/jumpserver From 69cd7bce17258358d18efa76ed903227bb14c683 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 20 Mar 2023 15:45:21 +0800 Subject: [PATCH 014/100] =?UTF-8?q?perf:=20=E6=89=B9=E9=87=8F=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=B4=A6=E5=8F=B7=E6=97=B6=EF=BC=8C=E8=B7=B3=E8=BF=87?= =?UTF-8?q?unique=E6=A3=80=E6=9F=A5=20=E4=B8=8D=E5=8E=BB=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=20(#9966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/serializers/account/account.py | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 295c8ed1f..739ef7e37 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -1,5 +1,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from rest_framework.validators import ( + UniqueTogetherValidator, ValidationError +) from accounts.const import SecretType, Source from accounts.models import Account, AccountTemplate @@ -8,8 +11,20 @@ from assets.const import Category, AllTypes from assets.models import Asset from common.serializers import SecretReadableMixin, BulkModelSerializer from common.serializers.fields import ObjectRelatedField, LabeledChoiceField +from common.utils import get_logger from .base import BaseAccountSerializer +logger = get_logger(__name__) + + +class SkipUniqueValidator(UniqueTogetherValidator): + def __call__(self, attrs, serializer): + try: + super().__call__(attrs, serializer) + except ValidationError as e: + logger.debug(f'{attrs.get("asset")}: {e.detail[0]}') + raise ValidationError({}) + class AccountSerializerCreateValidateMixin: from_id: str @@ -122,6 +137,18 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): .prefetch_related('asset', 'asset__platform', 'asset__platform__automation') return queryset + def get_validators(self): + validators = [] + data = self.context['request'].data + action = self.context['view'].action + _validators = super().get_validators() + ignore = action == 'create' and isinstance(data, list) and len(data) > 1 + for v in _validators: + if ignore and isinstance(v, UniqueTogetherValidator): + v = SkipUniqueValidator(v.queryset, v.fields) + validators.append(v) + return validators + class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): From 9413fd4cd16f1c1e5a824da89862fb30510deb1f Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 20 Mar 2023 17:26:47 +0800 Subject: [PATCH 015/100] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=20iframe=20?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E9=80=89=E6=8B=A9=20(#9908)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 支持 iframe 标签选择 * perf: 完善 iframe 的选择语法 --------- Co-authored-by: Eric --- apps/terminal/applets/chrome/app.py | 26 +++++++++++++++++++++++ apps/terminal/applets/chrome/manifest.yml | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/terminal/applets/chrome/app.py b/apps/terminal/applets/chrome/app.py index 6a2f678b5..ced2456c8 100644 --- a/apps/terminal/applets/chrome/app.py +++ b/apps/terminal/applets/chrome/app.py @@ -18,6 +18,7 @@ class Command(Enum): CLICK = 'click' OPEN = 'open' CODE = 'code' + SELECT_FRAME = 'select_frame' def _execute_type(ele: WebElement, value: str): @@ -53,6 +54,9 @@ class StepAction: def execute(self, driver: webdriver.Chrome) -> bool: if not self.target: return True + if self.command == 'select_frame': + self._switch_iframe(driver, self.target) + return True target_name, target_value = self.target.split("=", 1) by_name = self.methods_map.get(target_name.upper(), By.NAME) ele = driver.find_element(by=by_name, value=target_value) @@ -74,6 +78,28 @@ class StepAction: def _execute_command_type(self, ele, value): ele.send_keys(value) + def _switch_iframe(self, driver: webdriver.Chrome, target: str): + """ + driver: webdriver.Chrome + target: str + target support three format str below: + index=1: switch to frame by index, if index < 0, switch to default frame + id=xxx: switch to frame by id + name=xxx: switch to frame by name + """ + target_name, target_value = target.split("=", 1) + if target_name == 'id': + driver.switch_to.frame(target_value) + elif target_name == 'index': + index = int(target_value) + if index < 0: + driver.switch_to.default_content() + else: + driver.switch_to.frame(index) + elif target_name == 'name': + driver.switch_to.frame(target_value) + else: + driver.switch_to.frame(target) def execute_action(driver: webdriver.Chrome, step: StepAction) -> bool: try: diff --git a/apps/terminal/applets/chrome/manifest.yml b/apps/terminal/applets/chrome/manifest.yml index f2681a0c2..850ec3f08 100644 --- a/apps/terminal/applets/chrome/manifest.yml +++ b/apps/terminal/applets/chrome/manifest.yml @@ -1,6 +1,6 @@ name: chrome display_name: Chrome Browser -version: 0.1 +version: 0.2 comment: Chrome Browser Open URL Page Address author: JumpServer Team exec_type: python From cac59db1ec415930041d4bdb19787e691fa28b04 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 21 Mar 2023 12:43:54 +0800 Subject: [PATCH 016/100] =?UTF-8?q?perf:=20=E8=AE=BE=E7=BD=AE=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E8=B4=A6=E5=8F=B7=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC?= =?UTF-8?q?=EF=BC=8C=E6=96=B9=E4=BE=BF=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index d7dc7b175..dfcd2de82 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -71,7 +71,8 @@ class AssetAccountSerializer( template = serializers.BooleanField( default=False, label=_("Template"), write_only=True ) - name = serializers.CharField(max_length=128, required=True, label=_("Name")) + name = serializers.CharField(max_length=128, required=False, label=_("Name")) + secret_type = serializers.CharField(max_length=64, default='password', label=_("Secret type")) class Meta: model = Account From ed4a4ceca1b8dc239fe50650334388696b993b77 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 22 Mar 2023 11:09:48 +0800 Subject: [PATCH 017/100] =?UTF-8?q?perf:=20=E6=89=B9=E9=87=8F=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=B4=A6=E5=8F=B7=20=E5=AE=9A=E4=B9=89=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=B4=A6=E5=8F=B7=E7=AD=96=E7=95=A5=20=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E6=88=96=E6=8A=9B=E5=87=BA=E9=94=99=E8=AF=AF=20(#1002?= =?UTF-8?q?8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/const/account.py | 6 ++ apps/accounts/serializers/account/account.py | 40 ++++---- apps/accounts/validator.py | 98 ++++++++++++++++++++ 3 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 apps/accounts/validator.py diff --git a/apps/accounts/const/account.py b/apps/accounts/const/account.py index 109044934..b86e9400b 100644 --- a/apps/accounts/const/account.py +++ b/apps/accounts/const/account.py @@ -18,3 +18,9 @@ class AliasAccount(TextChoices): class Source(TextChoices): LOCAL = 'local', _('Local') COLLECTED = 'collected', _('Collected') + + +class BulkCreateStrategy(TextChoices): + SKIP = 'skip', _('Skip') + UPDATE = 'update', _('Update') + ERROR = 'error', _('Failed') diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 739ef7e37..a2ab1e22e 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -1,10 +1,11 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from rest_framework.validators import ( - UniqueTogetherValidator, ValidationError + UniqueTogetherValidator ) -from accounts.const import SecretType, Source +from accounts import validator +from accounts.const import SecretType, Source, BulkCreateStrategy from accounts.models import Account, AccountTemplate from accounts.tasks import push_accounts_to_assets_task from assets.const import Category, AllTypes @@ -17,15 +18,6 @@ from .base import BaseAccountSerializer logger = get_logger(__name__) -class SkipUniqueValidator(UniqueTogetherValidator): - def __call__(self, attrs, serializer): - try: - super().__call__(attrs, serializer) - except ValidationError as e: - logger.debug(f'{attrs.get("asset")}: {e.detail[0]}') - raise ValidationError({}) - - class AccountSerializerCreateValidateMixin: from_id: str template: bool @@ -113,12 +105,16 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): required=False, queryset=Account.objects, allow_null=True, allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username') ) + strategy = LabeledChoiceField( + choices=BulkCreateStrategy.choices, default=BulkCreateStrategy.SKIP, + write_only=True, label=_('Account policy') + ) class Meta(BaseAccountSerializer.Meta): model = Account fields = BaseAccountSerializer.Meta.fields + [ 'su_from', 'asset', 'template', 'version', - 'push_now', 'source', 'connectivity', + 'push_now', 'source', 'connectivity', 'strategy' ] extra_kwargs = { **BaseAccountSerializer.Meta.extra_kwargs, @@ -138,17 +134,27 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): return queryset def get_validators(self): - validators = [] - data = self.context['request'].data - action = self.context['view'].action + ignore = False + validators = [validator.AccountSecretTypeValidator(fields=('secret_type',))] + view = self.context.get('view') + request = self.context.get('request') + if request and view: + data = request.data + action = view.action + ignore = action == 'create' and isinstance(data, list) + _validators = super().get_validators() - ignore = action == 'create' and isinstance(data, list) and len(data) > 1 for v in _validators: if ignore and isinstance(v, UniqueTogetherValidator): - v = SkipUniqueValidator(v.queryset, v.fields) + v = validator.AccountUniqueTogetherValidator(v.queryset, v.fields) validators.append(v) return validators + def validate(self, attrs): + attrs = super().validate(attrs) + attrs.pop('strategy', None) + return attrs + class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): diff --git a/apps/accounts/validator.py b/apps/accounts/validator.py new file mode 100644 index 000000000..ccf1e8c11 --- /dev/null +++ b/apps/accounts/validator.py @@ -0,0 +1,98 @@ +from functools import reduce + +from django.utils.translation import ugettext_lazy as _ +from rest_framework.validators import ( + UniqueTogetherValidator, ValidationError +) + +from accounts.const import BulkCreateStrategy +from accounts.models import Account +from assets.const import Protocol + +__all__ = ['AccountUniqueTogetherValidator', 'AccountSecretTypeValidator'] + + +class ValidatorStrategyMixin: + + @staticmethod + def get_strategy(attrs): + return attrs.get('strategy', BulkCreateStrategy.SKIP) + + def __call__(self, attrs, serializer): + message = None + try: + super().__call__(attrs, serializer) + except ValidationError as e: + message = e.detail[0] + strategy = self.get_strategy(attrs) + if not message: + return + if strategy == BulkCreateStrategy.ERROR: + raise ValidationError(message, code='error') + elif strategy in [BulkCreateStrategy.SKIP, BulkCreateStrategy.UPDATE]: + raise ValidationError({}) + else: + return + + +class SecretTypeValidator: + requires_context = True + protocol_settings = Protocol.settings() + message = _('{field_name} not a legal option') + + def __init__(self, fields): + self.fields = fields + + def __call__(self, attrs, serializer): + secret_types = set() + asset = attrs['asset'] + secret_type = attrs['secret_type'] + platform_protocols_dict = { + name: self.protocol_settings.get(name, {}).get('secret_types', []) + for name in asset.platform.protocols.values_list('name', flat=True) + } + + for name in asset.protocols.values_list('name', flat=True): + if name in platform_protocols_dict: + secret_types |= set(platform_protocols_dict[name]) + if secret_type not in secret_types: + message = self.message.format(field_name=secret_type) + raise ValidationError(message, code='error') + + +class UpdateAccountMixin: + fields: tuple + get_strategy: callable + + def update(self, attrs): + unique_together = Account._meta.unique_together + unique_together_fields = reduce(lambda x, y: set(x) | set(y), unique_together) + query = {field_name: attrs[field_name] for field_name in unique_together_fields} + account = Account.objects.filter(**query).first() + if not account: + query = {field_name: attrs[field_name] for field_name in self.fields} + account = Account.objects.filter(**query).first() + + for k, v in attrs.items(): + setattr(account, k, v) + account.save() + + def __call__(self, attrs, serializer): + try: + super().__call__(attrs, serializer) + except ValidationError as e: + strategy = self.get_strategy(attrs) + if strategy == BulkCreateStrategy.UPDATE: + self.update(attrs) + message = e.detail[0] + raise ValidationError(message, code='unique') + + +class AccountUniqueTogetherValidator( + ValidatorStrategyMixin, UpdateAccountMixin, UniqueTogetherValidator +): + pass + + +class AccountSecretTypeValidator(ValidatorStrategyMixin, SecretTypeValidator): + pass From 81b04c449a51f8e35f78d8c18928b921868941e4 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 22 Mar 2023 13:47:12 +0800 Subject: [PATCH 018/100] fix: tanslate (#10031) Co-authored-by: feng <1304903146@qq.com> --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 77 +++++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 77 +++++++++++++++------------- 4 files changed, 84 insertions(+), 78 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 64397eca5..74bd80fbd 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:03f75000def71efcfb08e7c1ee8122f6997b1f82be6cb747b52c33aa8b75a7bf -size 138164 +oid sha256:15e96f9f31e92077ac828e248a30678e53b7c867757ae6348ae9805bc64874bc +size 138124 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 46836bfc1..30c1483ae 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-03-19 23:52+0800\n" +"POT-Creation-Date: 2023-03-22 11:40+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -70,6 +70,22 @@ msgstr "ローカル" msgid "Collected" msgstr "" +#: accounts/const/account.py:24 ops/const.py:45 +msgid "Skip" +msgstr "スキップ" + +#: accounts/const/account.py:25 audits/const.py:23 rbac/tree.py:229 +#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6 +msgid "Update" +msgstr "更新" + +#: accounts/const/account.py:26 +#: accounts/serializers/automations/change_secret.py:155 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 +msgid "Failed" +msgstr "失敗しました" + #: accounts/const/automation.py:22 rbac/tree.py:50 msgid "Push account" msgstr "アカウントプッシュ" @@ -160,7 +176,7 @@ msgstr "作成のみ" #: accounts/models/account.py:47 #: accounts/models/automations/gather_account.py:14 -#: accounts/serializers/account/account.py:95 +#: accounts/serializers/account/account.py:102 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:131 @@ -176,7 +192,7 @@ msgstr "作成のみ" msgid "Asset" msgstr "資産" -#: accounts/models/account.py:51 accounts/serializers/account/account.py:99 +#: accounts/models/account.py:51 accounts/serializers/account/account.py:106 #: authentication/serializers/connect_token_secret.py:50 msgid "Su from" msgstr "から切り替え" @@ -186,7 +202,7 @@ msgstr "から切り替え" msgid "Version" msgstr "バージョン" -#: accounts/models/account.py:55 accounts/serializers/account/account.py:96 +#: accounts/models/account.py:55 accounts/serializers/account/account.py:103 #: users/models/user.py:768 msgid "Source" msgstr "ソース" @@ -328,7 +344,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:134 +#: accounts/serializers/account/account.py:167 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 #: authentication/serializers/connect_token_secret.py:41 @@ -338,6 +354,7 @@ msgstr "鍵の種類" #: accounts/models/automations/change_secret.py:20 #: accounts/models/automations/change_secret.py:90 accounts/models/base.py:38 +#: accounts/serializers/account/base.py:19 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:19 @@ -515,22 +532,22 @@ msgstr "" "{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" "情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" -#: accounts/serializers/account/account.py:65 +#: accounts/serializers/account/account.py:72 #: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75 msgid "Template" msgstr "テンプレート" -#: accounts/serializers/account/account.py:68 +#: accounts/serializers/account/account.py:75 #: assets/serializers/asset/common.py:69 msgid "Push now" msgstr "今すぐプッシュ" -#: accounts/serializers/account/account.py:70 +#: accounts/serializers/account/account.py:77 #: accounts/serializers/account/base.py:64 msgid "Has secret" msgstr "エスクローされたパスワード" -#: accounts/serializers/account/account.py:75 applications/models.py:11 +#: accounts/serializers/account/account.py:82 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:77 #: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8 #: assets/serializers/platform.py:93 assets/serializers/platform.py:133 @@ -539,7 +556,7 @@ msgstr "エスクローされたパスワード" msgid "Category" msgstr "カテゴリ" -#: accounts/serializers/account/account.py:76 +#: accounts/serializers/account/account.py:83 #: 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 @@ -558,11 +575,15 @@ msgstr "カテゴリ" msgid "Type" msgstr "タイプ" -#: accounts/serializers/account/account.py:91 +#: accounts/serializers/account/account.py:98 msgid "Asset not found" msgstr "資産が存在しません" -#: accounts/serializers/account/account.py:144 acls/models/base.py:98 +#: accounts/serializers/account/account.py:110 ops/models/base.py:19 +msgid "Account policy" +msgstr "アカウント ポリシー" + +#: accounts/serializers/account/account.py:177 acls/models/base.py:98 #: acls/models/login_acl.py:13 acls/serializers/base.py:55 #: 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 @@ -580,7 +601,7 @@ msgstr "資産が存在しません" msgid "User" msgstr "ユーザー" -#: accounts/serializers/account/account.py:145 +#: accounts/serializers/account/account.py:178 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:98 terminal/notifications.py:146 msgid "Date" @@ -607,10 +628,6 @@ msgstr "現在、メール送信のみがサポートされています" msgid "Asset type" msgstr "資産タイプ" -#: accounts/serializers/account/base.py:19 -msgid "Secret" -msgstr "キー/パスワード" - #: accounts/serializers/account/base.py:24 msgid "Key password" msgstr "キーパスワード" @@ -670,12 +687,6 @@ msgstr "自動タスク実行履歴" msgid "Success" msgstr "成功" -#: accounts/serializers/automations/change_secret.py:155 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 -msgid "Failed" -msgstr "失敗しました" - #: accounts/tasks/automation.py:24 msgid "Account execute automation" msgstr "アカウント実行の自動化" @@ -716,6 +727,11 @@ msgstr "パスワードには `\"` を含まない" msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" +#: accounts/validator.py:41 +#, python-brace-format +msgid "{field_name} not a legal option" +msgstr "" + #: acls/apps.py:7 msgid "Acls" msgstr "Acls" @@ -1776,11 +1792,6 @@ msgstr "ダウンロード" msgid "View" msgstr "表示" -#: audits/const.py:23 rbac/tree.py:229 templates/_csv_import_export.html:18 -#: templates/_csv_update_modal.html:6 -msgid "Update" -msgstr "更新" - #: audits/const.py:25 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:227 @@ -3331,10 +3342,6 @@ msgstr "特権アカウントのみ" msgid "Privileged First" msgstr "特権アカウント優先" -#: ops/const.py:45 -msgid "Skip" -msgstr "スキップ" - #: ops/const.py:50 msgid "Powershell" msgstr "PowerShell" @@ -3395,10 +3402,6 @@ msgstr "アルグ" msgid "Creator" msgstr "作成者" -#: ops/models/base.py:19 -msgid "Account policy" -msgstr "アカウント ポリシー" - #: ops/models/base.py:20 msgid "Last execution" msgstr "最後の実行" @@ -6582,7 +6585,7 @@ msgstr "MFAフォース有効化" #: users/serializers/user.py:92 msgid "Login blocked" -msgstr "ログインブロック" +msgstr "ログインがロックされました" #: users/serializers/user.py:95 users/serializers/user.py:169 msgid "Is OTP bound" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 176ff7f89..7cf7ca157 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:fffd0032a105eb0c991f4eed7aca4fb344d9e8ab834f7fd2451b164532f198d7 -size 113407 +oid sha256:43695645a64669ba25c4fdfd413ce497a07592c320071b399cbb4f54466441e3 +size 113361 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 78ee626de..f0c85b894 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-03-19 23:52+0800\n" +"POT-Creation-Date: 2023-03-22 11:40+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -69,6 +69,22 @@ msgstr "数据库" msgid "Collected" msgstr "收集" +#: accounts/const/account.py:24 ops/const.py:45 +msgid "Skip" +msgstr "跳过" + +#: accounts/const/account.py:25 audits/const.py:23 rbac/tree.py:229 +#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6 +msgid "Update" +msgstr "更新" + +#: accounts/const/account.py:26 +#: accounts/serializers/automations/change_secret.py:155 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 +msgid "Failed" +msgstr "失败" + #: accounts/const/automation.py:22 rbac/tree.py:50 msgid "Push account" msgstr "账号推送" @@ -159,7 +175,7 @@ msgstr "仅创建" #: accounts/models/account.py:47 #: accounts/models/automations/gather_account.py:14 -#: accounts/serializers/account/account.py:95 +#: accounts/serializers/account/account.py:102 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:131 @@ -175,7 +191,7 @@ msgstr "仅创建" msgid "Asset" msgstr "资产" -#: accounts/models/account.py:51 accounts/serializers/account/account.py:99 +#: accounts/models/account.py:51 accounts/serializers/account/account.py:106 #: authentication/serializers/connect_token_secret.py:50 msgid "Su from" msgstr "切换自" @@ -185,7 +201,7 @@ msgstr "切换自" msgid "Version" msgstr "版本" -#: accounts/models/account.py:55 accounts/serializers/account/account.py:96 +#: accounts/models/account.py:55 accounts/serializers/account/account.py:103 #: users/models/user.py:768 msgid "Source" msgstr "来源" @@ -327,7 +343,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:134 +#: accounts/serializers/account/account.py:167 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 #: authentication/serializers/connect_token_secret.py:41 @@ -337,6 +353,7 @@ msgstr "密文类型" #: accounts/models/automations/change_secret.py:20 #: accounts/models/automations/change_secret.py:90 accounts/models/base.py:38 +#: accounts/serializers/account/base.py:19 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:19 @@ -511,22 +528,22 @@ msgstr "" "{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" "密密码" -#: accounts/serializers/account/account.py:65 +#: accounts/serializers/account/account.py:72 #: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75 msgid "Template" msgstr "模板" -#: accounts/serializers/account/account.py:68 +#: accounts/serializers/account/account.py:75 #: assets/serializers/asset/common.py:69 msgid "Push now" msgstr "立即推送" -#: accounts/serializers/account/account.py:70 +#: accounts/serializers/account/account.py:77 #: accounts/serializers/account/base.py:64 msgid "Has secret" msgstr "已托管密码" -#: accounts/serializers/account/account.py:75 applications/models.py:11 +#: accounts/serializers/account/account.py:82 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:77 #: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8 #: assets/serializers/platform.py:93 assets/serializers/platform.py:133 @@ -535,7 +552,7 @@ msgstr "已托管密码" msgid "Category" msgstr "类别" -#: accounts/serializers/account/account.py:76 +#: accounts/serializers/account/account.py:83 #: 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 @@ -554,11 +571,15 @@ msgstr "类别" msgid "Type" msgstr "类型" -#: accounts/serializers/account/account.py:91 +#: accounts/serializers/account/account.py:98 msgid "Asset not found" msgstr "资产不存在" -#: accounts/serializers/account/account.py:144 acls/models/base.py:98 +#: accounts/serializers/account/account.py:110 ops/models/base.py:19 +msgid "Account policy" +msgstr "账号策略" + +#: accounts/serializers/account/account.py:177 acls/models/base.py:98 #: acls/models/login_acl.py:13 acls/serializers/base.py:55 #: 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 @@ -576,7 +597,7 @@ msgstr "资产不存在" msgid "User" msgstr "用户" -#: accounts/serializers/account/account.py:145 +#: accounts/serializers/account/account.py:178 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:98 terminal/notifications.py:146 msgid "Date" @@ -603,10 +624,6 @@ msgstr "当前只支持邮件发送" msgid "Asset type" msgstr "资产类型" -#: accounts/serializers/account/base.py:19 -msgid "Secret" -msgstr "密文" - #: accounts/serializers/account/base.py:24 msgid "Key password" msgstr "密钥密码" @@ -666,12 +683,6 @@ msgstr "自动化任务执行历史" msgid "Success" msgstr "成功" -#: accounts/serializers/automations/change_secret.py:155 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 -msgid "Failed" -msgstr "失败" - #: accounts/tasks/automation.py:24 msgid "Account execute automation" msgstr "账号执行自动化" @@ -712,6 +723,11 @@ msgstr "密码不能包含 `\"` 字符" msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" +#: accounts/validator.py:41 +#, python-brace-format +msgid "{field_name} not a legal option" +msgstr "" + #: acls/apps.py:7 msgid "Acls" msgstr "访问控制" @@ -1764,11 +1780,6 @@ msgstr "下载文件" msgid "View" msgstr "查看" -#: audits/const.py:23 rbac/tree.py:229 templates/_csv_import_export.html:18 -#: templates/_csv_update_modal.html:6 -msgid "Update" -msgstr "更新" - #: audits/const.py:25 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:227 @@ -3294,10 +3305,6 @@ msgstr "仅限特权账号" msgid "Privileged First" msgstr "特权账号优先" -#: ops/const.py:45 -msgid "Skip" -msgstr "跳过" - #: ops/const.py:50 msgid "Powershell" msgstr "PowerShell" @@ -3358,10 +3365,6 @@ msgstr "参数" msgid "Creator" msgstr "创建者" -#: ops/models/base.py:19 -msgid "Account policy" -msgstr "账号策略" - #: ops/models/base.py:20 msgid "Last execution" msgstr "最后执行" @@ -6496,7 +6499,7 @@ msgstr "强制 MFA" #: users/serializers/user.py:92 msgid "Login blocked" -msgstr "登录被阻塞" +msgstr "登录被锁定" #: users/serializers/user.py:95 users/serializers/user.py:169 msgid "Is OTP bound" From 180ded1773572ce3f2b856f9a52167b3c3d931f3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 14:15:25 +0800 Subject: [PATCH 019/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platform?= =?UTF-8?q?=20protocols?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/base.py | 8 ++--- .../migrations/0111_auto_20230321_1633.py | 35 +++++++++++++++++++ apps/assets/models/asset/common.py | 19 ---------- apps/assets/models/platform.py | 23 ++---------- apps/assets/serializers/platform.py | 19 +++++++--- apps/ops/ansible/inventory.py | 9 ++++- 6 files changed, 64 insertions(+), 49 deletions(-) create mode 100644 apps/assets/migrations/0111_auto_20230321_1633.py diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index aa5070d5e..4b147f8ef 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -6,8 +6,10 @@ from .protocol import Protocol class BaseType(TextChoices): """ - 约束应该考虑代是对平台对限制,避免多余对选项,如: mysql 开启 ssh, 或者开启了也没有作用, 比如 k8s 开启了 domain,目前还不支持 + 约束应该考虑代是对平台对限制,避免多余对选项,如: mysql 开启 ssh, + 或者开启了也没有作用, 比如 k8s 开启了 domain,目前还不支持 """ + @classmethod def get_constrains(cls): constrains = {} @@ -36,7 +38,7 @@ class BaseType(TextChoices): if choices == '__self__': choices = [tp] protocols = [{'name': name, **settings.get(name, {})} for name in choices] - protocols[0]['primary'] = True + protocols[0]['default'] = True return protocols @classmethod @@ -74,5 +76,3 @@ class BaseType(TextChoices): choice for choice in cls_choices if choice[0] in tps ] - - diff --git a/apps/assets/migrations/0111_auto_20230321_1633.py b/apps/assets/migrations/0111_auto_20230321_1633.py new file mode 100644 index 000000000..ad7d57982 --- /dev/null +++ b/apps/assets/migrations/0111_auto_20230321_1633.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.17 on 2023-03-21 08:33 + +from django.db import migrations, models + + +def migrate_platform_charset(apps, schema_editor): + platform_model = apps.get_model('assets', 'Platform') + platform_model.objects.filter(charset='utf8').update(charset='utf-8') + + +def migrate_platform_protocol_required(apps, schema_editor): + platform_model = apps.get_model('assets', 'Platform') + platforms = platform_model.objects.all() + + for platform in platforms: + p = platform.protocols.first() + p.primary = True + p.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_auto_20230315_1741'), + ] + + operations = [ + migrations.AddField( + model_name='platformprotocol', + name='primary', + field=models.BooleanField(default=False, verbose_name='Primary'), + ), + migrations.RunPython(migrate_platform_charset), + migrations.RunPython(migrate_platform_protocol_required), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 3a38699e8..8643f4a6c 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -191,25 +191,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): names.append(n.name + ':' + n.value) return names - @lazyproperty - def primary_protocol(self): - from assets.const.types import AllTypes - primary_protocol_name = AllTypes.get_primary_protocol_name(self.category, self.type) - protocol = self.protocols.filter(name=primary_protocol_name).first() - return protocol - - @lazyproperty - def protocol(self): - if not self.primary_protocol: - return 'none' - return self.primary_protocol.name - - @lazyproperty - def port(self): - if not self.primary_protocol: - return 0 - return self.primary_protocol.port - @lazyproperty def type(self): return self.platform.type diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 488525ccd..e2262af04 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -10,29 +10,17 @@ __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] class PlatformProtocol(models.Model): - SETTING_ATTRS = { - 'console': False, - 'security': 'any,tls,rdp', - 'sftp_enabled': True, - 'sftp_home': '/tmp' - } - default = models.BooleanField(default=False, verbose_name=_('Default')) - required = models.BooleanField(default=False, verbose_name=_('Required')) name = models.CharField(max_length=32, verbose_name=_('Name')) port = models.IntegerField(verbose_name=_('Port')) + primary = models.BooleanField(default=False, verbose_name=_('Primary')) + required = models.BooleanField(default=False, verbose_name=_('Required')) + default = models.BooleanField(default=False, verbose_name=_('Default')) setting = models.JSONField(verbose_name=_('Setting'), default=dict) platform = models.ForeignKey('Platform', on_delete=models.CASCADE, related_name='protocols') def __str__(self): return '{}/{}'.format(self.name, self.port) - @property - def primary(self): - primary_protocol_name = AllTypes.get_primary_protocol_name( - self.platform.category, self.platform.type - ) - return self.name == primary_protocol_name - @property def secret_types(self): return Protocol.settings().get(self.name, {}).get('secret_types') @@ -100,11 +88,6 @@ class Platform(JMSBaseModel): ) return linux.id - @property - def primary_protocol(self): - primary_protocol_name = AllTypes.get_primary_protocol_name(self.category, self.type) - return self.protocols.filter(name=primary_protocol_name).first() - def __str__(self): return self.name diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 15b4e7cca..c4b51934b 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -72,16 +72,15 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): } -class PlatformProtocolsSerializer(serializers.ModelSerializer): +class PlatformProtocolSerializer(serializers.ModelSerializer): setting = ProtocolSettingSerializer(required=False, allow_null=True) - primary = serializers.BooleanField(read_only=True, label=_("Primary")) class Meta: model = PlatformProtocol fields = [ "id", "name", "port", "primary", - "default", "required", "secret_types", - "setting", + "required", "default", + "secret_types", "setting", ] @@ -91,7 +90,7 @@ class PlatformSerializer(WritableNestedModelSerializer): ) type = LabeledChoiceField(choices=AllTypes.choices(), label=_("Type")) category = LabeledChoiceField(choices=Category.choices, label=_("Category")) - protocols = PlatformProtocolsSerializer( + protocols = PlatformProtocolSerializer( label=_("Protocols"), many=True, required=False ) automation = PlatformAutomationSerializer(label=_("Automation"), required=False) @@ -126,6 +125,16 @@ class PlatformSerializer(WritableNestedModelSerializer): ) return queryset + def validate_protocols(self, protocols): + if not protocols: + raise serializers.ValidationError(_("Protocols is required")) + primary = [p for p in protocols if p.get('primary')] + if not primary: + protocols[0]['primary'] = True + protocols[0]['default'] = False + self.initial_data['protocols'] = protocols + return protocols + class PlatformOpsMethodSerializer(serializers.Serializer): id = serializers.CharField(read_only=True) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 8c45cba39..6048d9472 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -100,12 +100,19 @@ class JMSInventory: host.update(self.make_proxy_command(gateway)) def asset_to_host(self, asset, account, automation, protocols, platform): + if protocols: + protocol = protocols[0].name + port = protocol[0].port + else: + protocol = 'null' + port = 0 + host = { 'name': '{}'.format(asset.name.replace(' ', '_')), 'jms_asset': { 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, - 'protocol': asset.protocol, 'port': asset.port, + 'protocol': protocol, 'port': port, 'spec_info': asset.spec_info, 'secret_info': asset.secret_info, 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, From 43d805d0ca850dca74dbe0d70e27c6b472db5359 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 22 Mar 2023 14:48:05 +0800 Subject: [PATCH 020/100] =?UTF-8?q?perf:=20=E9=85=8D=E7=BD=AECHANGE=5FAUTH?= =?UTF-8?q?=5FPLAN=5FSECURE=5FMODE=5FENABLED=20=E5=AF=B9=E6=94=B9=E5=AF=86?= =?UTF-8?q?=E7=9A=84=E7=89=B9=E6=9D=83=E8=B4=A6=E5=8F=B7=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=20(#10033)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/automations/change_secret/manager.py | 3 +++ apps/settings/serializers/security.py | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py index 41bad5bda..999fc2c75 100644 --- a/apps/accounts/automations/change_secret/manager.py +++ b/apps/accounts/automations/change_secret/manager.py @@ -76,6 +76,9 @@ class ChangeSecretManager(AccountBasePlaybookManager): 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) return accounts def host_callback( diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py index 8f3511936..1dc41b037 100644 --- a/apps/settings/serializers/security.py +++ b/apps/settings/serializers/security.py @@ -168,9 +168,6 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri SECURITY_LUNA_REMEMBER_AUTH = serializers.BooleanField( label=_("Remember manual auth") ) - CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = serializers.BooleanField( - label=_("Enable change auth secure mode") - ) SECURITY_INSECURE_COMMAND = serializers.BooleanField( required=False, label=_('Insecure command alert') ) From ba076f66129260786d2d55fad72eae0f28604d6f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 14:56:20 +0800 Subject: [PATCH 021/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0111_auto_20230321_1633.py | 4 ++-- apps/assets/serializers/platform.py | 1 + apps/ops/ansible/inventory.py | 14 +++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/assets/migrations/0111_auto_20230321_1633.py b/apps/assets/migrations/0111_auto_20230321_1633.py index ad7d57982..0f1ba7f14 100644 --- a/apps/assets/migrations/0111_auto_20230321_1633.py +++ b/apps/assets/migrations/0111_auto_20230321_1633.py @@ -8,7 +8,7 @@ def migrate_platform_charset(apps, schema_editor): platform_model.objects.filter(charset='utf8').update(charset='utf-8') -def migrate_platform_protocol_required(apps, schema_editor): +def migrate_platform_protocol_primary(apps, schema_editor): platform_model = apps.get_model('assets', 'Platform') platforms = platform_model.objects.all() @@ -31,5 +31,5 @@ class Migration(migrations.Migration): field=models.BooleanField(default=False, verbose_name='Primary'), ), migrations.RunPython(migrate_platform_charset), - migrations.RunPython(migrate_platform_protocol_required), + migrations.RunPython(migrate_platform_protocol_primary), ] diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index c4b51934b..21c6a124a 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -132,6 +132,7 @@ class PlatformSerializer(WritableNestedModelSerializer): if not primary: protocols[0]['primary'] = True protocols[0]['default'] = False + # 这里不设置不行,write_nested 不使用 validated 中的 self.initial_data['protocols'] = protocols return protocols diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 6048d9472..e3954c063 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -100,9 +100,17 @@ class JMSInventory: host.update(self.make_proxy_command(gateway)) def asset_to_host(self, asset, account, automation, protocols, platform): - if protocols: - protocol = protocols[0].name - port = protocol[0].port + primary_protocol = [p for p in protocols if p.primary] + if len(primary_protocol) >= 1: + primary = primary_protocol[0] + elif protocols: + primary = protocols[0] + else: + primary = None + + if primary: + protocol = primary.name + port = primary.port else: protocol = 'null' port = 0 From 59d9572d07b04cbadbe43ebe5616acf0465f0427 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 15:26:23 +0800 Subject: [PATCH 022/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20protocol?= =?UTF-8?q?=20=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index e3954c063..fb143cda5 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -99,7 +99,8 @@ class JMSInventory: if gateway: host.update(self.make_proxy_command(gateway)) - def asset_to_host(self, asset, account, automation, protocols, platform): + @staticmethod + def get_primary_protocol(protocols): primary_protocol = [p for p in protocols if p.primary] if len(primary_protocol) >= 1: primary = primary_protocol[0] @@ -114,6 +115,10 @@ class JMSInventory: else: protocol = 'null' port = 0 + return protocol, port + + def asset_to_host(self, asset, account, automation, protocols, platform): + protocol, port = self.get_primary_protocol(protocols) host = { 'name': '{}'.format(asset.name.replace(' ', '_')), From aca0d62febac9ea47e9a4fce45c4480ffb6e5e6f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 15:28:05 +0800 Subject: [PATCH 023/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20protocols?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/platform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 21c6a124a..7485224e7 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -131,7 +131,6 @@ class PlatformSerializer(WritableNestedModelSerializer): primary = [p for p in protocols if p.get('primary')] if not primary: protocols[0]['primary'] = True - protocols[0]['default'] = False # 这里不设置不行,write_nested 不使用 validated 中的 self.initial_data['protocols'] = protocols return protocols From c6071740b1eacef0530b201566cb90b60635c641 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 16:43:00 +0800 Subject: [PATCH 024/100] =?UTF-8?q?perf:=20=E7=A1=AC=E4=BB=B6=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=94=AF=E6=8C=81=E8=B4=A6=E5=8F=B7=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/device.py | 5 ++++- apps/assets/const/host.py | 4 ++-- apps/assets/serializers/platform.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index d00996dd6..1a9f206b2 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -15,7 +15,10 @@ class DeviceTypes(BaseType): '*': { 'charset_enabled': False, 'domain_enabled': True, - 'su_enabled': False, + 'su_enabled': True, + 'su_methods': [ + {'name': 'enable', 'id': 'enable'}, + ] } } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index eb7930b09..2139567a9 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -20,8 +20,8 @@ class HostTypes(BaseType): 'domain_enabled': True, 'su_enabled': True, 'su_methods': [ - {'name': 'sudo su', 'id': 'sudo su'}, - {'name': 'su -', 'id': 'su -'} + {'name': 'sudo su -', 'id': 'sudo'}, + {'name': 'su -', 'id': 'su'} ], }, cls.WINDOWS: { diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 7485224e7..53be193a6 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -95,7 +95,7 @@ class PlatformSerializer(WritableNestedModelSerializer): ) automation = PlatformAutomationSerializer(label=_("Automation"), required=False) su_method = LabeledChoiceField( - choices=[("sudo", "sudo su -"), ("su", "su - ")], + choices=[("sudo", "sudo su -"), ("su", "su - "), ("enable", "enable")], label=_("Su method"), required=False, default="sudo", allow_null=True ) From 47c207ce13ef78000e2ee8c0666e6e5d1562723e Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 16:43:00 +0800 Subject: [PATCH 025/100] =?UTF-8?q?perf:=20=E7=A1=AC=E4=BB=B6=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=94=AF=E6=8C=81=E8=B4=A6=E5=8F=B7=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/device.py | 5 ++++- apps/assets/const/host.py | 4 ++-- apps/assets/serializers/platform.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index d00996dd6..1a9f206b2 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -15,7 +15,10 @@ class DeviceTypes(BaseType): '*': { 'charset_enabled': False, 'domain_enabled': True, - 'su_enabled': False, + 'su_enabled': True, + 'su_methods': [ + {'name': 'enable', 'id': 'enable'}, + ] } } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index eb7930b09..2139567a9 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -20,8 +20,8 @@ class HostTypes(BaseType): 'domain_enabled': True, 'su_enabled': True, 'su_methods': [ - {'name': 'sudo su', 'id': 'sudo su'}, - {'name': 'su -', 'id': 'su -'} + {'name': 'sudo su -', 'id': 'sudo'}, + {'name': 'su -', 'id': 'su'} ], }, cls.WINDOWS: { diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 7485224e7..53be193a6 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -95,7 +95,7 @@ class PlatformSerializer(WritableNestedModelSerializer): ) automation = PlatformAutomationSerializer(label=_("Automation"), required=False) su_method = LabeledChoiceField( - choices=[("sudo", "sudo su -"), ("su", "su - ")], + choices=[("sudo", "sudo su -"), ("su", "su - "), ("enable", "enable")], label=_("Su method"), required=False, default="sudo", allow_null=True ) From 1e0a6b5072051ae3d361ccab1d808de925de3c9a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 17:17:49 +0800 Subject: [PATCH 026/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=B8=80?= =?UTF-8?q?=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/device.py | 4 +--- apps/assets/const/host.py | 5 +---- apps/assets/migrations/0111_auto_20230321_1633.py | 2 ++ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index 1a9f206b2..a3bc841a8 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -16,9 +16,7 @@ class DeviceTypes(BaseType): 'charset_enabled': False, 'domain_enabled': True, 'su_enabled': True, - 'su_methods': [ - {'name': 'enable', 'id': 'enable'}, - ] + 'su_methods': ['enable'] } } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 2139567a9..7b6c8818c 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -19,10 +19,7 @@ class HostTypes(BaseType): 'charset': 'utf-8', # default 'domain_enabled': True, 'su_enabled': True, - 'su_methods': [ - {'name': 'sudo su -', 'id': 'sudo'}, - {'name': 'su -', 'id': 'su'} - ], + 'su_methods': ['sudo', 'su'], }, cls.WINDOWS: { 'su_enabled': False, diff --git a/apps/assets/migrations/0111_auto_20230321_1633.py b/apps/assets/migrations/0111_auto_20230321_1633.py index 0f1ba7f14..314d2ed7f 100644 --- a/apps/assets/migrations/0111_auto_20230321_1633.py +++ b/apps/assets/migrations/0111_auto_20230321_1633.py @@ -14,6 +14,8 @@ def migrate_platform_protocol_primary(apps, schema_editor): for platform in platforms: p = platform.protocols.first() + if not p: + continue p.primary = True p.save() From f7ae23f7d9a6da8da9bc7939664303778d2a3920 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 22 Mar 2023 18:35:23 +0800 Subject: [PATCH 027/100] =?UTF-8?q?perf:=20=E5=AF=BC=E5=85=A5=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E8=B4=A6=E5=8F=B7=E6=A8=A1=E7=89=88api=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20(#10038)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com> --- apps/accounts/serializers/account/account.py | 20 ++++++++++++++------ apps/assets/serializers/asset/common.py | 9 ++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index a2ab1e22e..b8a5d84c0 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -1,8 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from rest_framework.validators import ( - UniqueTogetherValidator -) +from rest_framework.generics import get_object_or_404 +from rest_framework.validators import UniqueTogetherValidator from accounts import validator from accounts.const import SecretType, Source, BulkCreateStrategy @@ -29,16 +28,25 @@ class AccountSerializerCreateValidateMixin: ret = super().to_internal_value(data) self.from_id = from_id return ret + @staticmethod + def related_template_values(template: AccountTemplate, attrs): + ignore_fields = ['id', 'date_created', 'date_updated', 'org_id'] + field_names = [ + field.name for field in template._meta.fields + if field.name not in ignore_fields + ] + for name in field_names: + attrs[name] = attrs.get(name) or getattr(template, name) def set_secret(self, attrs): _id = self.from_id template = attrs.pop('template', None) if _id and template: - account_template = AccountTemplate.objects.get(id=_id) - attrs['secret'] = account_template.secret + account_template = get_object_or_404(AccountTemplate, id=_id) + self.related_template_values(account_template, attrs) elif _id and not template: - account = Account.objects.get(id=_id) + account = get_object_or_404(Account, id=_id) attrs['secret'] = account.secret return attrs diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index dfcd2de82..9f38156b3 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -6,9 +6,9 @@ from django.db.transaction import atomic from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from accounts.const import SecretType from accounts.models import Account -from accounts.serializers import AccountSerializerCreateValidateMixin -from accounts.serializers import AuthValidateMixin +from accounts.serializers import AuthValidateMixin, AccountSerializerCreateValidateMixin from common.serializers import WritableNestedModelSerializer, SecretReadableMixin, CommonModelSerializer from common.serializers.fields import LabeledChoiceField from orgs.mixins.serializers import BulkOrgResourceModelSerializer @@ -72,7 +72,10 @@ class AssetAccountSerializer( default=False, label=_("Template"), write_only=True ) name = serializers.CharField(max_length=128, required=False, label=_("Name")) - secret_type = serializers.CharField(max_length=64, default='password', label=_("Secret type")) + secret_type = LabeledChoiceField( + choices=SecretType.choices, default=SecretType.PASSWORD, + required=False, label=_('Secret type') + ) class Meta: model = Account From 0be3cb3c271ee91400d7f12ccba15ed326dda6a7 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 22 Mar 2023 19:14:43 +0800 Subject: [PATCH 028/100] fix: account update (#10039) Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/validator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/accounts/validator.py b/apps/accounts/validator.py index ccf1e8c11..b8c49896d 100644 --- a/apps/accounts/validator.py +++ b/apps/accounts/validator.py @@ -45,7 +45,10 @@ class SecretTypeValidator: def __call__(self, attrs, serializer): secret_types = set() - asset = attrs['asset'] + if serializer.instance: + asset = serializer.instance.asset + else: + asset = attrs['asset'] secret_type = attrs['secret_type'] platform_protocols_dict = { name: self.protocol_settings.get(name, {}).get('secret_types', []) From e3bd698baf687c44cb6fac50ead1496742317a44 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Mar 2023 19:42:51 +0800 Subject: [PATCH 029/100] =?UTF-8?q?feat:=20k8s=20=E6=94=AF=E6=8C=81=20gate?= =?UTF-8?q?way?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/cloud.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index 12c4f09dd..02410a6a9 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -15,6 +15,9 @@ class CloudTypes(BaseType): 'charset_enabled': False, 'domain_enabled': False, 'su_enabled': False, + }, + cls.K8S: { + 'domain_enabled': True, } } From f9b83b11fbfb90d73f9a56b019c84d1d4280a3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 23 Mar 2023 08:58:31 +0800 Subject: [PATCH 030/100] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E6=97=A7?= =?UTF-8?q?=E7=89=88=E6=9C=AC=20SSH=20=E6=9C=8D=E5=8A=A1=E7=AB=AF=E8=AE=A4?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile.loong64 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a7d6d2b9c..180f4c90f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ && mkdir -p /root/.ssh/ \ - && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ + && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 2d44b00df..dbffcc7a6 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -53,7 +53,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ && mkdir -p /root/.ssh/ \ - && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ + && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ From 3b45ad0c61118fc710ad01ea4ae418c8632ca94d Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 23 Mar 2023 11:20:48 +0800 Subject: [PATCH 031/100] feat: account remove secret (#10045) Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/api/account/account.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py index d12f11549..63ec374df 100644 --- a/apps/accounts/api/account/account.py +++ b/apps/accounts/api/account/account.py @@ -2,6 +2,7 @@ from django.shortcuts import get_object_or_404 from rest_framework.decorators import action from rest_framework.generics import ListAPIView from rest_framework.response import Response +from rest_framework.status import HTTP_200_OK from accounts import serializers from accounts.filters import AccountFilterSet @@ -29,6 +30,7 @@ class AccountViewSet(OrgBulkModelViewSet): 'partial_update': ['accounts.change_account'], 'su_from_accounts': 'accounts.view_account', 'username_suggestions': 'accounts.view_account', + 'remove_secret': 'accounts.change_account', } @action(methods=['get'], detail=False, url_path='su-from-accounts') @@ -71,6 +73,11 @@ class AccountViewSet(OrgBulkModelViewSet): usernames = common + others return Response(data=usernames) + @action(methods=['patch'], detail=False, url_path='remove-secret') + def remove_secret(self, request, *args, **kwargs): + account_ids = request.data.get('account_ids', []) + self.model.objects.filter(id__in=account_ids).update(secret=None) + return Response(status=HTTP_200_OK) class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): """ From f12e6af86ed5c7ddfbb9423239babd9284c29e96 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 23 Mar 2023 14:11:26 +0800 Subject: [PATCH 032/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/gateway.py | 13 ++++++++++--- apps/ops/ansible/inventory.py | 9 +-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/assets/models/gateway.py b/apps/assets/models/gateway.py index d9d4d891b..0fc6626df 100644 --- a/apps/assets/models/gateway.py +++ b/apps/assets/models/gateway.py @@ -2,11 +2,10 @@ # from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgManager -from assets.models.platform import Platform from assets.const import GATEWAY_NAME +from assets.models.platform import Platform from common.utils import get_logger, lazyproperty - +from orgs.mixins.models import OrgManager from .asset.host import Host logger = get_logger(__file__) @@ -57,6 +56,14 @@ class Gateway(Host): account = self.select_account return account.password if account else None + @lazyproperty + def port(self): + protocol = self.protocols.filter(name='ssh').first() + if protocol: + return protocol.port + else: + return '22' + @lazyproperty def private_key(self): account = self.select_account diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index fb143cda5..6abf6d18e 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -101,15 +101,8 @@ class JMSInventory: @staticmethod def get_primary_protocol(protocols): - primary_protocol = [p for p in protocols if p.primary] - if len(primary_protocol) >= 1: - primary = primary_protocol[0] - elif protocols: + if protocols: primary = protocols[0] - else: - primary = None - - if primary: protocol = primary.name port = primary.port else: From 166d074adb062f3e97ff5f8c317dc88b66f7677f Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 23 Mar 2023 14:13:08 +0800 Subject: [PATCH 033/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20port=20(#1?= =?UTF-8?q?0049)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ibuler --- apps/assets/models/gateway.py | 13 ++++++++++--- apps/ops/ansible/inventory.py | 9 +-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/assets/models/gateway.py b/apps/assets/models/gateway.py index d9d4d891b..0fc6626df 100644 --- a/apps/assets/models/gateway.py +++ b/apps/assets/models/gateway.py @@ -2,11 +2,10 @@ # from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgManager -from assets.models.platform import Platform from assets.const import GATEWAY_NAME +from assets.models.platform import Platform from common.utils import get_logger, lazyproperty - +from orgs.mixins.models import OrgManager from .asset.host import Host logger = get_logger(__file__) @@ -57,6 +56,14 @@ class Gateway(Host): account = self.select_account return account.password if account else None + @lazyproperty + def port(self): + protocol = self.protocols.filter(name='ssh').first() + if protocol: + return protocol.port + else: + return '22' + @lazyproperty def private_key(self): account = self.select_account diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index fb143cda5..6abf6d18e 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -101,15 +101,8 @@ class JMSInventory: @staticmethod def get_primary_protocol(protocols): - primary_protocol = [p for p in protocols if p.primary] - if len(primary_protocol) >= 1: - primary = primary_protocol[0] - elif protocols: + if protocols: primary = protocols[0] - else: - primary = None - - if primary: protocol = primary.name port = primary.port else: From 3959f4615a96b5af406448e7e42c084fdb45426c Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 23 Mar 2023 14:33:33 +0800 Subject: [PATCH 034/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c5a1d2d2a..ca31cce08 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运 - [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/) - [产品文档](https://docs.jumpserver.org) +- [在线学习](https://edu.fit2cloud.com/page/2635362) - [知识库](https://kb.fit2cloud.com/categories/jumpserver) ## 案例研究 From f6c5c35a2c75aa09e86d3ccf3e556c1ea764a8c8 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 23 Mar 2023 15:24:19 +0800 Subject: [PATCH 035/100] =?UTF-8?q?feat:=20=E8=B4=A6=E5=8F=B7=E6=94=B6?= =?UTF-8?q?=E9=9B=86=E6=89=B9=E9=87=8F=E5=90=8C=E6=AD=A5=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=20(#10051)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- .../api/automations/gather_accounts.py | 43 +++++++++++-------- apps/perms/serializers/permission.py | 2 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/apps/accounts/api/automations/gather_accounts.py b/apps/accounts/api/automations/gather_accounts.py index 3abca94dd..f4420f919 100644 --- a/apps/accounts/api/automations/gather_accounts.py +++ b/apps/accounts/api/automations/gather_accounts.py @@ -6,10 +6,9 @@ from rest_framework.decorators import action from rest_framework.response import Response from accounts import serializers -from accounts.const import AutomationTypes -from accounts.const import Source +from accounts.const import Source, AutomationTypes from accounts.filters import GatheredAccountFilterSet -from accounts.models import GatherAccountsAutomation +from accounts.models import GatherAccountsAutomation, Account from accounts.models import GatheredAccount from orgs.mixins.api import OrgBulkModelViewSet from .base import AutomationExecutionViewSet @@ -50,22 +49,28 @@ class GatheredAccountViewSet(OrgBulkModelViewSet): 'default': serializers.GatheredAccountSerializer, } rbac_perms = { - 'sync_account': 'assets.add_gatheredaccount', + 'sync_accounts': 'assets.add_gatheredaccount', } - @action(methods=['post'], detail=True, url_path='sync') - def sync_account(self, request, *args, **kwargs): - gathered_account = super().get_object() - asset = gathered_account.asset - username = gathered_account.username - accounts = asset.accounts.filter(username=username) - - if accounts.exists(): - accounts.update(source=Source.COLLECTED) - else: - asset.accounts.model.objects.create( - asset=asset, username=username, - name=f'{username}-{_("Collected")}', - source=Source.COLLECTED - ) + @action(methods=['post'], detail=False, url_path='sync-accounts') + def sync_accounts(self, request, *args, **kwargs): + gathered_account_ids = request.data.get('gathered_account_ids') + gathered_accounts = self.model.objects.filter(id__in=gathered_account_ids) + account_objs = [] + exists_accounts = Account.objects.none() + for gathered_account in gathered_accounts: + asset_id = gathered_account.asset_id + username = gathered_account.username + accounts = Account.objects.filter(asset_id=asset_id, username=username) + if accounts.exists(): + exists_accounts |= accounts + else: + account_objs.append( + Account( + asset_id=asset_id, username=username, + name=f'{username}-{_("Collected")}', + source=Source.COLLECTED + )) + exists_accounts.update(source=Source.COLLECTED) + Account.objects.bulk_create(account_objs) return Response(status=status.HTTP_201_CREATED) diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index 9260267cb..5fc19be29 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -109,7 +109,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['name'] = f"{account_data['name']}-clone" + 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) From 5d022c7056cd30b63389ee8917a798944aa25eec Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 23 Mar 2023 15:57:44 +0800 Subject: [PATCH 036/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E5=AF=BC=E5=85=A5=E6=94=AF=E6=8C=81=E5=A1=AB=E5=86=99?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 9f38156b3..f039e3ce7 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -133,7 +133,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali labels = AssetLabelSerializer(many=True, required=False, label=_('Label')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=()) accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Account')) - nodes_display = serializers.ListField(read_only=True, label=_("Node path")) + nodes_display = serializers.ListField(read_only=False, label=_("Node path")) class Meta: model = Asset From 55e1ef116b702a5c4040009e54b5d7e5dfcf54c2 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 23 Mar 2023 16:04:09 +0800 Subject: [PATCH 037/100] perf: clear secret (#10053) Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/api/account/account.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py index 63ec374df..5598f5f7b 100644 --- a/apps/accounts/api/account/account.py +++ b/apps/accounts/api/account/account.py @@ -30,7 +30,7 @@ class AccountViewSet(OrgBulkModelViewSet): 'partial_update': ['accounts.change_account'], 'su_from_accounts': 'accounts.view_account', 'username_suggestions': 'accounts.view_account', - 'remove_secret': 'accounts.change_account', + 'clear_secret': 'accounts.change_account', } @action(methods=['get'], detail=False, url_path='su-from-accounts') @@ -73,8 +73,8 @@ class AccountViewSet(OrgBulkModelViewSet): usernames = common + others return Response(data=usernames) - @action(methods=['patch'], detail=False, url_path='remove-secret') - def remove_secret(self, request, *args, **kwargs): + @action(methods=['patch'], detail=False, url_path='clear-secret') + def clear_secret(self, request, *args, **kwargs): account_ids = request.data.get('account_ids', []) self.model.objects.filter(id__in=account_ids).update(secret=None) return Response(status=HTTP_200_OK) From 30ab6836abac2f9dbddcf1c872510ab331aa0285 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 24 Mar 2023 17:02:11 +0800 Subject: [PATCH 038/100] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20login=5Fbut?= =?UTF-8?q?ton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/protocol.py | 2 +- apps/assets/serializers/asset/web.py | 2 +- apps/terminal/applets/chrome/test_data_example.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index 884a53785..6523c5dcc 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -116,7 +116,7 @@ class Protocol(ChoicesMixin, models.TextChoices): 'setting': { 'username_selector': 'name=username', 'password_selector': 'name=password', - 'submit_selector': 'id=longin_button', + 'submit_selector': 'id=login_button', } }, } diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py index aa35e28e0..c6e05f6c0 100644 --- a/apps/assets/serializers/asset/web.py +++ b/apps/assets/serializers/asset/web.py @@ -24,6 +24,6 @@ class WebSerializer(AssetSerializer): 'default': 'name=password' }, 'submit_selector': { - 'default': 'id=longin_button', + 'default': 'id=login_button', }, } diff --git a/apps/terminal/applets/chrome/test_data_example.json b/apps/terminal/applets/chrome/test_data_example.json index fc8e00991..417b0163d 100644 --- a/apps/terminal/applets/chrome/test_data_example.json +++ b/apps/terminal/applets/chrome/test_data_example.json @@ -24,7 +24,7 @@ "autofill": "basic", "username_selector": "name=username", "password_selector": "name=password", - "submit_selector": "id=longin_button", + "submit_selector": "id=login_button", "script": [] }, "org_id": "2925D985-A435-411D-9BC4-FEA630F105D9" From 66d368f882550adf29a6ab9ec262f8814ccac64b Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 24 Mar 2023 17:08:41 +0800 Subject: [PATCH 039/100] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9nodes=5Fdispla?= =?UTF-8?q?y=20required=20=3D=20False?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index f039e3ce7..0a544a7b2 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -133,7 +133,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali labels = AssetLabelSerializer(many=True, required=False, label=_('Label')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=()) accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Account')) - nodes_display = serializers.ListField(read_only=False, label=_("Node path")) + nodes_display = serializers.ListField(read_only=False, required=False, label=_("Node path")) class Meta: model = Asset From 1ac2fec13fbdf8f58676d79c6a55ceb17d4af6c2 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 23 Mar 2023 18:57:22 +0800 Subject: [PATCH 040/100] =?UTF-8?q?feat:=20=E6=94=B6=E9=9B=86=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=20=E5=8F=AF=E9=80=89=E5=90=8C=E6=AD=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/automations/gather_accounts.py | 23 ++----------- .../automations/gather_accounts/manager.py | 33 ++++++++++++------ ...atheraccountsautomation_is_sync_account.py | 18 ++++++++++ .../models/automations/gather_account.py | 34 ++++++++++++++++++- .../automations/gather_accounts.py | 3 +- 5 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 apps/accounts/migrations/0010_gatheraccountsautomation_is_sync_account.py diff --git a/apps/accounts/api/automations/gather_accounts.py b/apps/accounts/api/automations/gather_accounts.py index f4420f919..e6a846368 100644 --- a/apps/accounts/api/automations/gather_accounts.py +++ b/apps/accounts/api/automations/gather_accounts.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ from rest_framework import status from rest_framework.decorators import action from rest_framework.response import Response from accounts import serializers -from accounts.const import Source, AutomationTypes +from accounts.const import AutomationTypes from accounts.filters import GatheredAccountFilterSet -from accounts.models import GatherAccountsAutomation, Account +from accounts.models import GatherAccountsAutomation from accounts.models import GatheredAccount from orgs.mixins.api import OrgBulkModelViewSet from .base import AutomationExecutionViewSet @@ -56,21 +55,5 @@ class GatheredAccountViewSet(OrgBulkModelViewSet): def sync_accounts(self, request, *args, **kwargs): gathered_account_ids = request.data.get('gathered_account_ids') gathered_accounts = self.model.objects.filter(id__in=gathered_account_ids) - account_objs = [] - exists_accounts = Account.objects.none() - for gathered_account in gathered_accounts: - asset_id = gathered_account.asset_id - username = gathered_account.username - accounts = Account.objects.filter(asset_id=asset_id, username=username) - if accounts.exists(): - exists_accounts |= accounts - else: - account_objs.append( - Account( - asset_id=asset_id, username=username, - name=f'{username}-{_("Collected")}', - source=Source.COLLECTED - )) - exists_accounts.update(source=Source.COLLECTED) - Account.objects.bulk_create(account_objs) + self.model.sync_accounts(gathered_accounts) return Response(status=status.HTTP_201_CREATED) diff --git a/apps/accounts/automations/gather_accounts/manager.py b/apps/accounts/automations/gather_accounts/manager.py index 2ecd3d2e1..c4ba6b5a0 100644 --- a/apps/accounts/automations/gather_accounts/manager.py +++ b/apps/accounts/automations/gather_accounts/manager.py @@ -12,6 +12,7 @@ class GatherAccountsManager(AccountBasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.host_asset_mapper = {} + self.is_sync_account = self.execution.snapshot.get('is_sync_account') @classmethod def method_type(cls): @@ -25,26 +26,38 @@ class GatherAccountsManager(AccountBasePlaybookManager): def filter_success_result(self, tp, result): result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result) return result - @staticmethod - def update_or_create_gathered_accounts(asset, result): + def generate_data(asset, result): + data = [] + for username, info in result.items(): + d = {'asset': asset, 'username': username, 'present': True} + if info.get('date'): + d['date_last_login'] = info['date'] + if info.get('address'): + d['address_last_login'] = info['address'][:32] + data.append(d) + return data + + def update_or_create_accounts(self, asset, result): + data = self.generate_data(asset, result) with tmp_to_org(asset.org_id): + gathered_accounts = [] GatheredAccount.objects.filter(asset=asset, present=True).update(present=False) - for username, data in result.items(): - d = {'asset': asset, 'username': username, 'present': True} - if data.get('date'): - d['date_last_login'] = data['date'] - if data.get('address'): - d['address_last_login'] = data['address'][:32] - GatheredAccount.objects.update_or_create( + for d in data: + username = d['username'] + gathered_account, __ = GatheredAccount.objects.update_or_create( defaults=d, asset=asset, username=username, ) + gathered_accounts.append(gathered_account) + if not self.is_sync_account: + return + GatheredAccount.sync_accounts(gathered_accounts) def on_host_success(self, host, result): info = result.get('debug', {}).get('res', {}).get('info', {}) asset = self.host_asset_mapper.get(host) if asset and info: result = self.filter_success_result(asset.type, info) - self.update_or_create_gathered_accounts(asset, result) + self.update_or_create_accounts(asset, result) else: logger.error("Not found info".format(host)) diff --git a/apps/accounts/migrations/0010_gatheraccountsautomation_is_sync_account.py b/apps/accounts/migrations/0010_gatheraccountsautomation_is_sync_account.py new file mode 100644 index 000000000..058a7dd7e --- /dev/null +++ b/apps/accounts/migrations/0010_gatheraccountsautomation_is_sync_account.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2023-03-23 08:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0009_account_usernames_to_ids'), + ] + + operations = [ + migrations.AddField( + model_name='gatheraccountsautomation', + name='is_sync_account', + field=models.BooleanField(blank=True, default=False, verbose_name='Is sync account'), + ), + ] diff --git a/apps/accounts/models/automations/gather_account.py b/apps/accounts/models/automations/gather_account.py index 01f903141..dd9b5c862 100644 --- a/apps/accounts/models/automations/gather_account.py +++ b/apps/accounts/models/automations/gather_account.py @@ -1,7 +1,9 @@ from django.db import models +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from accounts.const import AutomationTypes +from accounts.const import AutomationTypes, Source +from accounts.models import Account from orgs.mixins.models import JMSOrgBaseModel from .base import AccountBaseAutomation @@ -19,6 +21,25 @@ class GatheredAccount(JMSOrgBaseModel): def address(self): return self.asset.address + @staticmethod + def sync_accounts(gathered_accounts): + account_objs = [] + for gathered_account in gathered_accounts: + asset_id = gathered_account.asset_id + username = gathered_account.username + accounts = Account.objects.filter( + Q(asset_id=asset_id, username=username) | + Q(asset_id=asset_id, name=username) + ) + if accounts.exists(): + continue + account = Account( + asset_id=asset_id, username=username, + name=username, source=Source.COLLECTED + ) + account_objs.append(account) + Account.objects.bulk_create(account_objs) + class Meta: verbose_name = _('Gather account automation') unique_together = [ @@ -31,6 +52,17 @@ class GatheredAccount(JMSOrgBaseModel): class GatherAccountsAutomation(AccountBaseAutomation): + is_sync_account = models.BooleanField( + default=False, blank=True, verbose_name=_("Is sync account") + ) + + def to_attr_json(self): + attr_json = super().to_attr_json() + attr_json.update({ + 'is_sync_account': self.is_sync_account, + }) + return attr_json + def save(self, *args, **kwargs): self.type = AutomationTypes.gather_accounts super().save(*args, **kwargs) diff --git a/apps/accounts/serializers/automations/gather_accounts.py b/apps/accounts/serializers/automations/gather_accounts.py index b906e7881..fd09773de 100644 --- a/apps/accounts/serializers/automations/gather_accounts.py +++ b/apps/accounts/serializers/automations/gather_accounts.py @@ -17,7 +17,8 @@ class GatherAccountAutomationSerializer(BaseAutomationSerializer): class Meta: model = GatherAccountsAutomation read_only_fields = BaseAutomationSerializer.Meta.read_only_fields - fields = BaseAutomationSerializer.Meta.fields + read_only_fields + fields = BaseAutomationSerializer.Meta.fields \ + + ['is_sync_account'] + read_only_fields extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs From 017682b383f438d5ba4aaef75f9f7f31951773b3 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 27 Mar 2023 14:37:04 +0800 Subject: [PATCH 041/100] =?UTF-8?q?perf:=20=E5=A2=9E=E5=8A=A0=E6=88=91?= =?UTF-8?q?=E7=9A=84=E8=B5=84=E4=BA=A7=E9=80=9A=E8=BF=87=20node=5Fid=20?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E6=88=91=E7=9A=84=E8=B5=84=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/assets.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/perms/api/user_permission/assets.py b/apps/perms/api/user_permission/assets.py index f94854164..1ec27803f 100644 --- a/apps/perms/api/user_permission/assets.py +++ b/apps/perms/api/user_permission/assets.py @@ -4,7 +4,7 @@ from rest_framework.generics import ListAPIView from assets.api.asset.asset import AssetFilterSet from assets.models import Asset, Node -from common.utils import get_logger, lazyproperty +from common.utils import get_logger, lazyproperty, is_uuid from perms import serializers from perms.pagination import AllPermedAssetPagination from perms.pagination import NodePermedAssetPagination @@ -58,7 +58,12 @@ class UserAllPermedAssetsApi(BaseUserPermedAssetsApi): pagination_class = AllPermedAssetPagination def get_assets(self): - return self.query_asset_util.get_all_assets() + node_id = self.request.query_params.get('node_id') + if is_uuid(node_id): + __, assets = self.query_asset_util.get_node_all_assets(node_id) + else: + assets = self.query_asset_util.get_all_assets() + return assets class UserDirectPermedAssetsApi(BaseUserPermedAssetsApi): From 118d33fa02d932ceccff4a51b68566a2e796d138 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 27 Mar 2023 18:06:30 +0800 Subject: [PATCH 042/100] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=20super=20?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E7=9A=84=E5=88=87=E6=8D=A2=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/device.py | 2 +- apps/assets/serializers/platform.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index a3bc841a8..5e8f8f879 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -16,7 +16,7 @@ class DeviceTypes(BaseType): 'charset_enabled': False, 'domain_enabled': True, 'su_enabled': True, - 'su_methods': ['enable'] + 'su_methods': ['enable', 'super', 'super_level'] } } diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 53be193a6..20824cba9 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -85,6 +85,14 @@ class PlatformProtocolSerializer(serializers.ModelSerializer): class PlatformSerializer(WritableNestedModelSerializer): + SU_METHOD_CHOICES = [ + ("sudo", "sudo su -"), + ("su", "su - "), + ("enable", "enable"), + ("super", "super 15"), + ("super_level", "super level 15") + ] + charset = LabeledChoiceField( choices=Platform.CharsetChoices.choices, label=_("Charset") ) @@ -94,10 +102,9 @@ class PlatformSerializer(WritableNestedModelSerializer): label=_("Protocols"), many=True, required=False ) automation = PlatformAutomationSerializer(label=_("Automation"), required=False) - su_method = LabeledChoiceField( - choices=[("sudo", "sudo su -"), ("su", "su - "), ("enable", "enable")], - label=_("Su method"), required=False, default="sudo", allow_null=True - ) + su_method = LabeledChoiceField(choices=SU_METHOD_CHOICES, + label=_("Su method"), required=False, default="sudo", allow_null=True + ) class Meta: model = Platform From 5b87470b5c8ac39cec09063e0a0339b6e3b8fa40 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 27 Mar 2023 17:17:20 +0800 Subject: [PATCH 043/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E6=B4=BB=E5=8A=A8=E6=97=A5=E5=BF=97=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E7=9A=84=E6=8F=90=E7=A4=BA=E5=8F=8A=E6=93=8D=E4=BD=9C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E7=9A=84=E5=AD=97=E6=AE=B5=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/accounts/api/account/account.py | 1 + apps/common/views/mixins.py | 67 ++++++--- apps/locale/ja/LC_MESSAGES/django.po | 204 ++++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.po | 204 ++++++++++++++------------- 4 files changed, 254 insertions(+), 222 deletions(-) diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py index 5598f5f7b..72e926d4a 100644 --- a/apps/accounts/api/account/account.py +++ b/apps/accounts/api/account/account.py @@ -79,6 +79,7 @@ class AccountViewSet(OrgBulkModelViewSet): self.model.objects.filter(id__in=account_ids).update(secret=None) return Response(status=HTTP_200_OK) + class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): """ 因为可能要导出所有账号,所以单独建立了一个 viewset diff --git a/apps/common/views/mixins.py b/apps/common/views/mixins.py index cb2e402c8..562e9ca81 100644 --- a/apps/common/views/mixins.py +++ b/apps/common/views/mixins.py @@ -1,15 +1,17 @@ # -*- coding: utf-8 -*- # from django.utils import translation -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_noop from django.contrib.auth.mixins import UserPassesTestMixin from django.http.response import JsonResponse from rest_framework import permissions from rest_framework.request import Request from common.exceptions import UserConfirmRequired +from common.utils import i18n_fmt from audits.handler import create_or_update_operate_log -from audits.const import ActionChoices +from audits.const import ActionChoices, ActivityChoices +from audits.models import ActivityLog __all__ = [ "PermissionsMixin", @@ -49,38 +51,63 @@ class RecordViewLogMixin: ACTION = ActionChoices.view @staticmethod - def get_resource_display(request): + def _filter_params(params): + new_params = {} + need_pop_params = ('format', 'order') + for key, value in params.items(): + if key in need_pop_params: + continue + if isinstance(value, list): + value = list(filter(None, value)) + if value: + new_params[key] = value + return new_params + + def get_resource_display(self, request): query_params = dict(request.query_params) - if query_params.get("format"): - query_params.pop("format") - spm_filter = query_params.pop("spm") if query_params.get("spm") else None - if not query_params and not spm_filter: - display_message = _("Export all") + params = self._filter_params(query_params) + + spm_filter = params.pop("spm", None) + + if not params and not spm_filter: + display_message = gettext_noop("Export all") elif spm_filter: - display_message = _("Export only selected items") + display_message = gettext_noop("Export only selected items") else: query = ",".join( - ["%s=%s" % (key, value) for key, value in query_params.items()] + ["%s=%s" % (key, value) for key, value in params.items()] ) - display_message = _("Export filtered: %s") % query + display_message = i18n_fmt(gettext_noop("Export filtered: %s"), query) return display_message + def record_logs(self, ids, **kwargs): + resource_type = self.model._meta.verbose_name + create_or_update_operate_log( + self.ACTION, resource_type, force=True, **kwargs + ) + detail = i18n_fmt( + gettext_noop('User %s view/export secret'), self.request.user + ) + activities = [ + ActivityLog( + resource_id=getattr(resource_id, 'pk', resource_id), + type=ActivityChoices.operate_log, detail=detail + ) + for resource_id in ids + ] + ActivityLog.objects.bulk_create(activities) + def list(self, request, *args, **kwargs): response = super().list(request, *args, **kwargs) with translation.override('en'): resource_display = self.get_resource_display(request) - resource_type = self.model._meta.verbose_name - create_or_update_operate_log( - self.ACTION, resource_type, force=True, - resource_display=resource_display - ) + ids = [q.id for q in self.get_queryset()] + self.record_logs(ids, resource_display=resource_display) return response def retrieve(self, request, *args, **kwargs): response = super().retrieve(request, *args, **kwargs) with translation.override('en'): - resource_type = self.model._meta.verbose_name - create_or_update_operate_log( - self.ACTION, resource_type, force=True, resource=self.get_object() - ) + resource = self.get_object() + self.record_logs([resource.id], resource=resource) return response diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 30c1483ae..e9145956c 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-03-22 11:40+0800\n" +"POT-Creation-Date: 2023-03-27 17:00+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -175,13 +175,13 @@ msgid "Only create" msgstr "作成のみ" #: accounts/models/account.py:47 -#: accounts/models/automations/gather_account.py:14 -#: accounts/serializers/account/account.py:102 +#: accounts/models/automations/gather_account.py:16 +#: accounts/serializers/account/account.py:110 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:131 #: acls/models/base.py:100 acls/serializers/base.py:56 -#: assets/models/asset/common.py:92 assets/models/asset/common.py:280 +#: assets/models/asset/common.py:92 assets/models/asset/common.py:261 #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19 #: assets/serializers/label.py:27 audits/models.py:48 #: authentication/models/connection_token.py:33 @@ -192,7 +192,7 @@ msgstr "作成のみ" msgid "Asset" msgstr "資産" -#: accounts/models/account.py:51 accounts/serializers/account/account.py:106 +#: accounts/models/account.py:51 accounts/serializers/account/account.py:114 #: authentication/serializers/connect_token_secret.py:50 msgid "Su from" msgstr "から切り替え" @@ -202,7 +202,7 @@ msgstr "から切り替え" msgid "Version" msgstr "バージョン" -#: accounts/models/account.py:55 accounts/serializers/account/account.py:103 +#: accounts/models/account.py:55 accounts/serializers/account/account.py:111 #: users/models/user.py:768 msgid "Source" msgstr "ソース" @@ -211,7 +211,7 @@ msgstr "ソース" #: accounts/serializers/automations/change_secret.py:112 #: accounts/serializers/automations/change_secret.py:132 #: acls/models/base.py:102 acls/serializers/base.py:57 -#: assets/serializers/asset/common.py:131 assets/serializers/gateway.py:28 +#: assets/serializers/asset/common.py:135 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 #: terminal/backends/command/models.py:21 terminal/models/session/session.py:34 @@ -344,9 +344,10 @@ msgid "Can add push account execution" msgstr "プッシュ アカウントの作成の実行" #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 -#: accounts/serializers/account/account.py:167 +#: accounts/serializers/account/account.py:175 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 +#: assets/serializers/asset/common.py:77 #: authentication/serializers/connect_token_secret.py:41 #: authentication/serializers/connect_token_secret.py:51 msgid "Secret type" @@ -402,15 +403,15 @@ msgstr "間違い" msgid "Change secret record" msgstr "パスワード レコードの変更" -#: accounts/models/automations/gather_account.py:12 +#: accounts/models/automations/gather_account.py:14 msgid "Present" msgstr "存在する" -#: accounts/models/automations/gather_account.py:13 +#: accounts/models/automations/gather_account.py:15 msgid "Date last login" msgstr "最終ログイン日" -#: accounts/models/automations/gather_account.py:15 +#: 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 #: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25 @@ -423,11 +424,11 @@ msgstr "最終ログイン日" msgid "Username" msgstr "ユーザー名" -#: accounts/models/automations/gather_account.py:16 +#: accounts/models/automations/gather_account.py:18 msgid "Address last login" msgstr "最終ログインアドレス" -#: accounts/models/automations/gather_account.py:23 +#: accounts/models/automations/gather_account.py:44 msgid "Gather account automation" msgstr "自動収集アカウント" @@ -462,9 +463,9 @@ msgstr "アカウントの確認" #: assets/models/asset/common.py:90 assets/models/asset/common.py:102 #: 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:21 assets/models/platform.py:76 -#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:151 -#: assets/serializers/platform.py:132 +#: assets/models/platform.py:13 assets/models/platform.py:64 +#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:155 +#: assets/serializers/platform.py:141 #: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 @@ -532,36 +533,36 @@ msgstr "" "{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" "情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" -#: accounts/serializers/account/account.py:72 +#: accounts/serializers/account/account.py:80 #: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75 msgid "Template" msgstr "テンプレート" -#: accounts/serializers/account/account.py:75 +#: accounts/serializers/account/account.py:83 #: assets/serializers/asset/common.py:69 msgid "Push now" msgstr "今すぐプッシュ" -#: accounts/serializers/account/account.py:77 +#: accounts/serializers/account/account.py:85 #: accounts/serializers/account/base.py:64 msgid "Has secret" msgstr "エスクローされたパスワード" -#: accounts/serializers/account/account.py:82 applications/models.py:11 -#: assets/models/label.py:21 assets/models/platform.py:77 -#: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:93 assets/serializers/platform.py:133 +#: accounts/serializers/account/account.py:90 applications/models.py:11 +#: assets/models/label.py:21 assets/models/platform.py:65 +#: assets/serializers/asset/common.py:131 assets/serializers/cagegory.py:8 +#: assets/serializers/platform.py:92 assets/serializers/platform.py:142 #: 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:83 +#: accounts/serializers/account/account.py:91 #: 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:78 -#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:92 +#: assets/models/cmd_filter.py:74 assets/models/platform.py:66 +#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:91 #: audits/serializers.py:48 #: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 @@ -575,15 +576,15 @@ msgstr "カテゴリ" msgid "Type" msgstr "タイプ" -#: accounts/serializers/account/account.py:98 +#: accounts/serializers/account/account.py:106 msgid "Asset not found" msgstr "資産が存在しません" -#: accounts/serializers/account/account.py:110 ops/models/base.py:19 +#: accounts/serializers/account/account.py:118 ops/models/base.py:19 msgid "Account policy" msgstr "アカウント ポリシー" -#: accounts/serializers/account/account.py:177 acls/models/base.py:98 +#: accounts/serializers/account/account.py:185 acls/models/base.py:98 #: acls/models/login_acl.py:13 acls/serializers/base.py:55 #: 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 @@ -601,7 +602,7 @@ msgstr "アカウント ポリシー" msgid "User" msgstr "ユーザー" -#: accounts/serializers/account/account.py:178 +#: accounts/serializers/account/account.py:186 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:98 terminal/notifications.py:146 msgid "Date" @@ -633,7 +634,7 @@ msgid "Key password" msgstr "キーパスワード" #: accounts/serializers/account/base.py:81 -#: assets/serializers/asset/common.py:303 +#: assets/serializers/asset/common.py:307 msgid "Spec info" msgstr "特別情報" @@ -1018,7 +1019,7 @@ msgid "Device" msgstr "インターネット機器" #: assets/const/category.py:13 assets/models/asset/database.py:9 -#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:115 +#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:119 msgid "Database" msgstr "データベース" @@ -1082,7 +1083,7 @@ msgid "Basic" msgstr "基本" #: assets/const/web.py:61 assets/models/asset/web.py:13 -#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:39 +#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:39 msgid "Script" msgstr "脚本" @@ -1205,17 +1206,17 @@ msgstr "システムユーザーに一致できます" msgid "Cloud" msgstr "クラウド サービス" -#: assets/models/asset/common.py:91 assets/models/platform.py:22 +#: assets/models/asset/common.py:91 assets/models/platform.py:14 #: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "ポート" -#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:152 +#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:156 msgid "Address" msgstr "アドレス" -#: assets/models/asset/common.py:104 assets/models/platform.py:112 +#: assets/models/asset/common.py:104 assets/models/platform.py:95 #: authentication/serializers/connect_token_secret.py:108 #: perms/serializers/user_permission.py:24 #: xpack/plugins/cloud/serializers/account_attrs.py:197 @@ -1236,23 +1237,23 @@ msgstr "ラベル" msgid "Info" msgstr "情報" -#: assets/models/asset/common.py:283 +#: assets/models/asset/common.py:264 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" -#: assets/models/asset/common.py:284 +#: assets/models/asset/common.py:265 msgid "Can test asset connectivity" msgstr "資産接続をテストできます" -#: assets/models/asset/common.py:285 +#: assets/models/asset/common.py:266 msgid "Can match asset" msgstr "アセットを一致させることができます" -#: assets/models/asset/common.py:286 +#: assets/models/asset/common.py:267 msgid "Can change asset nodes" msgstr "資産ノードを変更できます" -#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:116 +#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:120 #: settings/serializers/email.py:37 msgid "Use SSL" msgstr "SSLの使用" @@ -1269,7 +1270,7 @@ msgstr "クライアント証明書" msgid "Client key" msgstr "クライアントキー" -#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:117 +#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:121 msgid "Allow invalid cert" msgstr "証明書チェックを無視" @@ -1277,23 +1278,23 @@ msgstr "証明書チェックを無視" msgid "Autofill" msgstr "自動充填" -#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:120 +#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:124 #: assets/serializers/platform.py:31 msgid "Username selector" msgstr "ユーザー名ピッカー" -#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:121 +#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:125 #: assets/serializers/platform.py:34 msgid "Password selector" msgstr "パスワードセレクター" -#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:122 +#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:126 #: assets/serializers/platform.py:37 msgid "Submit selector" msgstr "ボタンセレクターを確認する" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:302 rbac/tree.py:35 +#: assets/serializers/asset/common.py:306 rbac/tree.py:35 msgid "Accounts" msgstr "アカウント" @@ -1366,7 +1367,7 @@ msgstr "コマンドフィルタルール" msgid "Favorite asset" msgstr "お気に入りのアセット" -#: assets/models/gateway.py:35 assets/serializers/domain.py:16 +#: assets/models/gateway.py:34 assets/serializers/domain.py:16 msgid "Gateway" msgstr "ゲートウェイ" @@ -1374,7 +1375,7 @@ msgstr "ゲートウェイ" msgid "Asset group" msgstr "資産グループ" -#: assets/models/group.py:34 assets/models/platform.py:19 +#: assets/models/group.py:34 assets/models/platform.py:17 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" msgstr "デフォルト" @@ -1395,7 +1396,7 @@ msgstr "システム" msgid "Value" msgstr "値" -#: assets/models/label.py:40 assets/serializers/asset/common.py:129 +#: assets/models/label.py:40 assets/serializers/asset/common.py:133 #: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13 #: authentication/serializers/connect_token_secret.py:114 #: common/serializers/common.py:79 settings/serializers/sms.py:7 @@ -1431,91 +1432,95 @@ msgstr "ノード" msgid "Can match node" msgstr "ノードを一致させることができます" -#: assets/models/platform.py:20 +#: assets/models/platform.py:15 +msgid "Primary" +msgstr "主要" + +#: assets/models/platform.py:16 msgid "Required" msgstr "必要" -#: assets/models/platform.py:23 settings/serializers/settings.py:65 +#: assets/models/platform.py:18 settings/serializers/settings.py:65 #: users/templates/users/reset_password.html:29 msgid "Setting" msgstr "設定" -#: assets/models/platform.py:42 audits/const.py:47 settings/models.py:37 +#: assets/models/platform.py:30 audits/const.py:47 settings/models.py:37 #: terminal/serializers/applet_host.py:29 msgid "Enabled" msgstr "有効化" -#: assets/models/platform.py:43 +#: assets/models/platform.py:31 msgid "Ansible config" msgstr "Ansible 構成" -#: assets/models/platform.py:44 assets/serializers/platform.py:60 +#: assets/models/platform.py:32 assets/serializers/platform.py:60 msgid "Ping enabled" msgstr "アセット ディスカバリを有効にする" -#: assets/models/platform.py:45 assets/serializers/platform.py:61 +#: assets/models/platform.py:33 assets/serializers/platform.py:61 msgid "Ping method" msgstr "資産検出方法" -#: assets/models/platform.py:46 assets/models/platform.py:59 +#: assets/models/platform.py:34 assets/models/platform.py:47 #: assets/serializers/platform.py:62 msgid "Gather facts enabled" msgstr "資産情報の収集を有効にする" -#: assets/models/platform.py:47 assets/models/platform.py:61 +#: assets/models/platform.py:35 assets/models/platform.py:49 #: assets/serializers/platform.py:63 msgid "Gather facts method" msgstr "情報収集の方法" -#: assets/models/platform.py:48 assets/serializers/platform.py:66 +#: assets/models/platform.py:36 assets/serializers/platform.py:66 msgid "Change secret enabled" msgstr "パスワードの変更が有効" -#: assets/models/platform.py:50 assets/serializers/platform.py:67 +#: assets/models/platform.py:38 assets/serializers/platform.py:67 msgid "Change secret method" msgstr "パスワード変更モード" -#: assets/models/platform.py:52 assets/serializers/platform.py:68 +#: assets/models/platform.py:40 assets/serializers/platform.py:68 msgid "Push account enabled" msgstr "アカウントのプッシュを有効にする" -#: assets/models/platform.py:54 assets/serializers/platform.py:69 +#: assets/models/platform.py:42 assets/serializers/platform.py:69 msgid "Push account method" msgstr "アカウントプッシュ方式" -#: assets/models/platform.py:56 assets/serializers/platform.py:64 +#: assets/models/platform.py:44 assets/serializers/platform.py:64 msgid "Verify account enabled" msgstr "アカウントの確認をオンにする" -#: assets/models/platform.py:58 assets/serializers/platform.py:65 +#: assets/models/platform.py:46 assets/serializers/platform.py:65 msgid "Verify account method" msgstr "アカウント認証方法" -#: assets/models/platform.py:79 tickets/models/ticket/general.py:300 +#: assets/models/platform.py:67 tickets/models/ticket/general.py:300 msgid "Meta" msgstr "メタ" -#: assets/models/platform.py:80 +#: assets/models/platform.py:68 msgid "Internal" msgstr "ビルトイン" -#: assets/models/platform.py:83 assets/serializers/platform.py:90 +#: assets/models/platform.py:71 assets/serializers/platform.py:89 msgid "Charset" msgstr "シャーセット" -#: assets/models/platform.py:85 assets/serializers/platform.py:118 +#: assets/models/platform.py:73 assets/serializers/platform.py:117 msgid "Domain enabled" msgstr "ドメインを有効にする" -#: assets/models/platform.py:87 assets/serializers/platform.py:117 +#: assets/models/platform.py:75 assets/serializers/platform.py:116 msgid "Su enabled" msgstr "アカウントの切り替えを有効にする" -#: assets/models/platform.py:88 assets/serializers/platform.py:100 +#: assets/models/platform.py:76 assets/serializers/platform.py:99 msgid "Su method" msgstr "アカウントの切り替え方法" -#: assets/models/platform.py:90 assets/serializers/platform.py:97 +#: assets/models/platform.py:78 assets/serializers/platform.py:96 msgid "Automation" msgstr "オートメーション" @@ -1532,36 +1537,36 @@ msgstr "" "プラットフォームタイプがスキップされた資産に合致しない、資産内の一括更新プ" "ラットフォーム" -#: assets/serializers/asset/common.py:119 +#: assets/serializers/asset/common.py:123 msgid "Auto fill" msgstr "自動充填" -#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:95 +#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:94 #: authentication/serializers/connect_token_secret.py:28 #: authentication/serializers/connect_token_secret.py:66 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 msgid "Protocols" msgstr "プロトコル" -#: assets/serializers/asset/common.py:132 -#: assets/serializers/asset/common.py:153 +#: assets/serializers/asset/common.py:136 +#: assets/serializers/asset/common.py:157 msgid "Node path" msgstr "ノードパスです" -#: assets/serializers/asset/common.py:150 -#: assets/serializers/asset/common.py:304 +#: assets/serializers/asset/common.py:154 +#: assets/serializers/asset/common.py:308 msgid "Auto info" msgstr "自動情報" -#: assets/serializers/asset/common.py:228 +#: assets/serializers/asset/common.py:232 msgid "Platform not exist" msgstr "プラットフォームが存在しません" -#: assets/serializers/asset/common.py:263 +#: assets/serializers/asset/common.py:267 msgid "port out of range (1-65535)" msgstr "ポート番号が範囲外です (1-65535)" -#: assets/serializers/asset/common.py:270 +#: assets/serializers/asset/common.py:274 msgid "Protocol is required: {}" msgstr "プロトコルが必要です: {}" @@ -1670,11 +1675,7 @@ msgstr "アカウント収集を有効にする" msgid "Gather accounts method" msgstr "アカウントの収集方法" -#: assets/serializers/platform.py:77 -msgid "Primary" -msgstr "主要" - -#: assets/serializers/platform.py:119 +#: assets/serializers/platform.py:118 msgid "Default Domain" msgstr "デフォルト ドメイン" @@ -3173,19 +3174,24 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: common/views/mixins.py:58 +#: common/views/mixins.py:73 msgid "Export all" msgstr "すべてエクスポート" -#: common/views/mixins.py:60 +#: common/views/mixins.py:75 msgid "Export only selected items" msgstr "選択項目のみエクスポート" -#: common/views/mixins.py:65 +#: common/views/mixins.py:80 #, python-format msgid "Export filtered: %s" msgstr "検索のエクスポート: %s" +#: common/views/mixins.py:90 +#, python-format +msgid "User %s view/export secret" +msgstr "ユーザー %s がパスワードを閲覧/導き出しました" + #: jumpserver/conf.py:416 msgid "Create account successfully" msgstr "アカウントを正常に作成" @@ -3262,11 +3268,11 @@ msgstr "投稿サイトニュース" msgid "No account available" msgstr "利用可能なアカウントがありません" -#: ops/ansible/inventory.py:196 +#: ops/ansible/inventory.py:209 msgid "Ansible disabled" msgstr "Ansible 無効" -#: ops/ansible/inventory.py:212 +#: ops/ansible/inventory.py:225 msgid "Skip hosts below:" msgstr "次のホストをスキップします: " @@ -4931,43 +4937,39 @@ msgid "Remember manual auth" msgstr "手動入力パスワードの保存" #: settings/serializers/security.py:172 -msgid "Enable change auth secure mode" -msgstr "安全モードの変更を有効にする" - -#: settings/serializers/security.py:175 msgid "Insecure command alert" msgstr "安全でないコマンドアラート" -#: settings/serializers/security.py:178 +#: settings/serializers/security.py:175 msgid "Email recipient" msgstr "メール受信者" -#: settings/serializers/security.py:179 +#: settings/serializers/security.py:176 msgid "Multiple user using , split" msgstr "複数のユーザーを使用して、分割" -#: settings/serializers/security.py:182 +#: settings/serializers/security.py:179 msgid "Operation center" msgstr "職業センター" -#: settings/serializers/security.py:183 +#: settings/serializers/security.py:180 msgid "Allow user run batch command or not using ansible" msgstr "ユーザー実行バッチコマンドを許可するか、ansibleを使用しない" -#: settings/serializers/security.py:186 +#: settings/serializers/security.py:183 msgid "Session share" msgstr "セッション共有" -#: settings/serializers/security.py:187 +#: settings/serializers/security.py:184 msgid "Enabled, Allows user active session to be shared with other users" msgstr "" "ユーザーのアクティブなセッションを他のユーザーと共有できるようにします。" -#: settings/serializers/security.py:190 +#: settings/serializers/security.py:187 msgid "Remote Login Protection" msgstr "リモートログイン保護" -#: settings/serializers/security.py:192 +#: settings/serializers/security.py:189 msgid "" "The system determines whether the login IP address belongs to a common login " "city. If the account is logged in from a common login city, the system sends " diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index f0c85b894..a86f2df06 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-03-22 11:40+0800\n" +"POT-Creation-Date: 2023-03-27 17:00+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -174,13 +174,13 @@ msgid "Only create" msgstr "仅创建" #: accounts/models/account.py:47 -#: accounts/models/automations/gather_account.py:14 -#: accounts/serializers/account/account.py:102 +#: accounts/models/automations/gather_account.py:16 +#: accounts/serializers/account/account.py:110 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:131 #: acls/models/base.py:100 acls/serializers/base.py:56 -#: assets/models/asset/common.py:92 assets/models/asset/common.py:280 +#: assets/models/asset/common.py:92 assets/models/asset/common.py:261 #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19 #: assets/serializers/label.py:27 audits/models.py:48 #: authentication/models/connection_token.py:33 @@ -191,7 +191,7 @@ msgstr "仅创建" msgid "Asset" msgstr "资产" -#: accounts/models/account.py:51 accounts/serializers/account/account.py:106 +#: accounts/models/account.py:51 accounts/serializers/account/account.py:114 #: authentication/serializers/connect_token_secret.py:50 msgid "Su from" msgstr "切换自" @@ -201,7 +201,7 @@ msgstr "切换自" msgid "Version" msgstr "版本" -#: accounts/models/account.py:55 accounts/serializers/account/account.py:103 +#: accounts/models/account.py:55 accounts/serializers/account/account.py:111 #: users/models/user.py:768 msgid "Source" msgstr "来源" @@ -210,7 +210,7 @@ msgstr "来源" #: accounts/serializers/automations/change_secret.py:112 #: accounts/serializers/automations/change_secret.py:132 #: acls/models/base.py:102 acls/serializers/base.py:57 -#: assets/serializers/asset/common.py:131 assets/serializers/gateway.py:28 +#: assets/serializers/asset/common.py:135 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 #: terminal/backends/command/models.py:21 terminal/models/session/session.py:34 @@ -343,9 +343,10 @@ msgid "Can add push account execution" msgstr "创建推送账号执行" #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 -#: accounts/serializers/account/account.py:167 +#: accounts/serializers/account/account.py:175 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 +#: assets/serializers/asset/common.py:77 #: authentication/serializers/connect_token_secret.py:41 #: authentication/serializers/connect_token_secret.py:51 msgid "Secret type" @@ -401,15 +402,15 @@ msgstr "错误" msgid "Change secret record" msgstr "改密记录" -#: accounts/models/automations/gather_account.py:12 +#: accounts/models/automations/gather_account.py:14 msgid "Present" msgstr "存在" -#: accounts/models/automations/gather_account.py:13 +#: accounts/models/automations/gather_account.py:15 msgid "Date last login" msgstr "最后登录日期" -#: accounts/models/automations/gather_account.py:15 +#: 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 #: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25 @@ -422,11 +423,11 @@ msgstr "最后登录日期" msgid "Username" msgstr "用户名" -#: accounts/models/automations/gather_account.py:16 +#: accounts/models/automations/gather_account.py:18 msgid "Address last login" msgstr "最后登录地址" -#: accounts/models/automations/gather_account.py:23 +#: accounts/models/automations/gather_account.py:44 msgid "Gather account automation" msgstr "自动化收集账号" @@ -461,9 +462,9 @@ msgstr "账号验证" #: assets/models/asset/common.py:90 assets/models/asset/common.py:102 #: 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:21 assets/models/platform.py:76 -#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:151 -#: assets/serializers/platform.py:132 +#: assets/models/platform.py:13 assets/models/platform.py:64 +#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:155 +#: assets/serializers/platform.py:141 #: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 @@ -528,36 +529,36 @@ msgstr "" "{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" "密密码" -#: accounts/serializers/account/account.py:72 +#: accounts/serializers/account/account.py:80 #: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75 msgid "Template" msgstr "模板" -#: accounts/serializers/account/account.py:75 +#: accounts/serializers/account/account.py:83 #: assets/serializers/asset/common.py:69 msgid "Push now" msgstr "立即推送" -#: accounts/serializers/account/account.py:77 +#: accounts/serializers/account/account.py:85 #: accounts/serializers/account/base.py:64 msgid "Has secret" msgstr "已托管密码" -#: accounts/serializers/account/account.py:82 applications/models.py:11 -#: assets/models/label.py:21 assets/models/platform.py:77 -#: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:93 assets/serializers/platform.py:133 +#: accounts/serializers/account/account.py:90 applications/models.py:11 +#: assets/models/label.py:21 assets/models/platform.py:65 +#: assets/serializers/asset/common.py:131 assets/serializers/cagegory.py:8 +#: assets/serializers/platform.py:92 assets/serializers/platform.py:142 #: 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:83 +#: accounts/serializers/account/account.py:91 #: 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:78 -#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:92 +#: assets/models/cmd_filter.py:74 assets/models/platform.py:66 +#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:91 #: audits/serializers.py:48 #: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 @@ -571,15 +572,15 @@ msgstr "类别" msgid "Type" msgstr "类型" -#: accounts/serializers/account/account.py:98 +#: accounts/serializers/account/account.py:106 msgid "Asset not found" msgstr "资产不存在" -#: accounts/serializers/account/account.py:110 ops/models/base.py:19 +#: accounts/serializers/account/account.py:118 ops/models/base.py:19 msgid "Account policy" msgstr "账号策略" -#: accounts/serializers/account/account.py:177 acls/models/base.py:98 +#: accounts/serializers/account/account.py:185 acls/models/base.py:98 #: acls/models/login_acl.py:13 acls/serializers/base.py:55 #: 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 @@ -597,7 +598,7 @@ msgstr "账号策略" msgid "User" msgstr "用户" -#: accounts/serializers/account/account.py:178 +#: accounts/serializers/account/account.py:186 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:98 terminal/notifications.py:146 msgid "Date" @@ -629,7 +630,7 @@ msgid "Key password" msgstr "密钥密码" #: accounts/serializers/account/base.py:81 -#: assets/serializers/asset/common.py:303 +#: assets/serializers/asset/common.py:307 msgid "Spec info" msgstr "特殊信息" @@ -1010,7 +1011,7 @@ msgid "Device" msgstr "网络设备" #: assets/const/category.py:13 assets/models/asset/database.py:9 -#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:115 +#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:119 msgid "Database" msgstr "数据库" @@ -1074,7 +1075,7 @@ msgid "Basic" msgstr "基本" #: assets/const/web.py:61 assets/models/asset/web.py:13 -#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:39 +#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:39 msgid "Script" msgstr "脚本" @@ -1197,17 +1198,17 @@ msgstr "可以匹配系统用户" msgid "Cloud" msgstr "云服务" -#: assets/models/asset/common.py:91 assets/models/platform.py:22 +#: assets/models/asset/common.py:91 assets/models/platform.py:14 #: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "端口" -#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:152 +#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:156 msgid "Address" msgstr "地址" -#: assets/models/asset/common.py:104 assets/models/platform.py:112 +#: assets/models/asset/common.py:104 assets/models/platform.py:95 #: authentication/serializers/connect_token_secret.py:108 #: perms/serializers/user_permission.py:24 #: xpack/plugins/cloud/serializers/account_attrs.py:197 @@ -1228,23 +1229,23 @@ msgstr "标签管理" msgid "Info" msgstr "信息" -#: assets/models/asset/common.py:283 +#: assets/models/asset/common.py:264 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" -#: assets/models/asset/common.py:284 +#: assets/models/asset/common.py:265 msgid "Can test asset connectivity" msgstr "可以测试资产连接性" -#: assets/models/asset/common.py:285 +#: assets/models/asset/common.py:266 msgid "Can match asset" msgstr "可以匹配资产" -#: assets/models/asset/common.py:286 +#: assets/models/asset/common.py:267 msgid "Can change asset nodes" msgstr "可以修改资产节点" -#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:116 +#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:120 #: settings/serializers/email.py:37 msgid "Use SSL" msgstr "使用 SSL" @@ -1261,7 +1262,7 @@ msgstr "客户端证书" msgid "Client key" msgstr "客户端密钥" -#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:117 +#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:121 msgid "Allow invalid cert" msgstr "忽略证书校验" @@ -1269,23 +1270,23 @@ msgstr "忽略证书校验" msgid "Autofill" msgstr "自动代填" -#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:120 +#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:124 #: assets/serializers/platform.py:31 msgid "Username selector" msgstr "用户名选择器" -#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:121 +#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:125 #: assets/serializers/platform.py:34 msgid "Password selector" msgstr "密码选择器" -#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:122 +#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:126 #: assets/serializers/platform.py:37 msgid "Submit selector" msgstr "确认按钮选择器" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:302 rbac/tree.py:35 +#: assets/serializers/asset/common.py:306 rbac/tree.py:35 msgid "Accounts" msgstr "账号管理" @@ -1358,7 +1359,7 @@ msgstr "命令过滤规则" msgid "Favorite asset" msgstr "收藏的资产" -#: assets/models/gateway.py:35 assets/serializers/domain.py:16 +#: assets/models/gateway.py:34 assets/serializers/domain.py:16 msgid "Gateway" msgstr "网关" @@ -1366,7 +1367,7 @@ msgstr "网关" msgid "Asset group" msgstr "资产组" -#: assets/models/group.py:34 assets/models/platform.py:19 +#: assets/models/group.py:34 assets/models/platform.py:17 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" msgstr "默认" @@ -1387,7 +1388,7 @@ msgstr "系统" msgid "Value" msgstr "值" -#: assets/models/label.py:40 assets/serializers/asset/common.py:129 +#: assets/models/label.py:40 assets/serializers/asset/common.py:133 #: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13 #: authentication/serializers/connect_token_secret.py:114 #: common/serializers/common.py:79 settings/serializers/sms.py:7 @@ -1423,91 +1424,95 @@ msgstr "节点" msgid "Can match node" msgstr "可以匹配节点" -#: assets/models/platform.py:20 +#: assets/models/platform.py:15 +msgid "Primary" +msgstr "主要的" + +#: assets/models/platform.py:16 msgid "Required" msgstr "必须的" -#: assets/models/platform.py:23 settings/serializers/settings.py:65 +#: assets/models/platform.py:18 settings/serializers/settings.py:65 #: users/templates/users/reset_password.html:29 msgid "Setting" msgstr "设置" -#: assets/models/platform.py:42 audits/const.py:47 settings/models.py:37 +#: assets/models/platform.py:30 audits/const.py:47 settings/models.py:37 #: terminal/serializers/applet_host.py:29 msgid "Enabled" msgstr "启用" -#: assets/models/platform.py:43 +#: assets/models/platform.py:31 msgid "Ansible config" msgstr "Ansible 配置" -#: assets/models/platform.py:44 assets/serializers/platform.py:60 +#: assets/models/platform.py:32 assets/serializers/platform.py:60 msgid "Ping enabled" msgstr "启用资产探活" -#: assets/models/platform.py:45 assets/serializers/platform.py:61 +#: assets/models/platform.py:33 assets/serializers/platform.py:61 msgid "Ping method" msgstr "资产探活方式" -#: assets/models/platform.py:46 assets/models/platform.py:59 +#: assets/models/platform.py:34 assets/models/platform.py:47 #: assets/serializers/platform.py:62 msgid "Gather facts enabled" msgstr "启用收集资产信息" -#: assets/models/platform.py:47 assets/models/platform.py:61 +#: assets/models/platform.py:35 assets/models/platform.py:49 #: assets/serializers/platform.py:63 msgid "Gather facts method" msgstr "收集信息方式" -#: assets/models/platform.py:48 assets/serializers/platform.py:66 +#: assets/models/platform.py:36 assets/serializers/platform.py:66 msgid "Change secret enabled" msgstr "启用改密" -#: assets/models/platform.py:50 assets/serializers/platform.py:67 +#: assets/models/platform.py:38 assets/serializers/platform.py:67 msgid "Change secret method" msgstr "改密方式" -#: assets/models/platform.py:52 assets/serializers/platform.py:68 +#: assets/models/platform.py:40 assets/serializers/platform.py:68 msgid "Push account enabled" msgstr "启用账号推送" -#: assets/models/platform.py:54 assets/serializers/platform.py:69 +#: assets/models/platform.py:42 assets/serializers/platform.py:69 msgid "Push account method" msgstr "账号推送方式" -#: assets/models/platform.py:56 assets/serializers/platform.py:64 +#: assets/models/platform.py:44 assets/serializers/platform.py:64 msgid "Verify account enabled" msgstr "开启账号验证" -#: assets/models/platform.py:58 assets/serializers/platform.py:65 +#: assets/models/platform.py:46 assets/serializers/platform.py:65 msgid "Verify account method" msgstr "账号验证方式" -#: assets/models/platform.py:79 tickets/models/ticket/general.py:300 +#: assets/models/platform.py:67 tickets/models/ticket/general.py:300 msgid "Meta" msgstr "元数据" -#: assets/models/platform.py:80 +#: assets/models/platform.py:68 msgid "Internal" msgstr "内置" -#: assets/models/platform.py:83 assets/serializers/platform.py:90 +#: assets/models/platform.py:71 assets/serializers/platform.py:89 msgid "Charset" msgstr "编码" -#: assets/models/platform.py:85 assets/serializers/platform.py:118 +#: assets/models/platform.py:73 assets/serializers/platform.py:117 msgid "Domain enabled" msgstr "启用网域" -#: assets/models/platform.py:87 assets/serializers/platform.py:117 +#: assets/models/platform.py:75 assets/serializers/platform.py:116 msgid "Su enabled" msgstr "启用账号切换" -#: assets/models/platform.py:88 assets/serializers/platform.py:100 +#: assets/models/platform.py:76 assets/serializers/platform.py:99 msgid "Su method" msgstr "账号切换方式" -#: assets/models/platform.py:90 assets/serializers/platform.py:97 +#: assets/models/platform.py:78 assets/serializers/platform.py:96 msgid "Automation" msgstr "自动化" @@ -1522,36 +1527,36 @@ msgid "" "type" msgstr "资产中批量更新平台,不符合平台类型跳过的资产" -#: assets/serializers/asset/common.py:119 +#: assets/serializers/asset/common.py:123 msgid "Auto fill" msgstr "自动代填" -#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:95 +#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:94 #: authentication/serializers/connect_token_secret.py:28 #: authentication/serializers/connect_token_secret.py:66 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 msgid "Protocols" msgstr "协议组" -#: assets/serializers/asset/common.py:132 -#: assets/serializers/asset/common.py:153 +#: assets/serializers/asset/common.py:136 +#: assets/serializers/asset/common.py:157 msgid "Node path" msgstr "节点路径" -#: assets/serializers/asset/common.py:150 -#: assets/serializers/asset/common.py:304 +#: assets/serializers/asset/common.py:154 +#: assets/serializers/asset/common.py:308 msgid "Auto info" msgstr "自动化信息" -#: assets/serializers/asset/common.py:228 +#: assets/serializers/asset/common.py:232 msgid "Platform not exist" msgstr "平台不存在" -#: assets/serializers/asset/common.py:263 +#: assets/serializers/asset/common.py:267 msgid "port out of range (1-65535)" msgstr "端口超出范围 (1-65535)" -#: assets/serializers/asset/common.py:270 +#: assets/serializers/asset/common.py:274 msgid "Protocol is required: {}" msgstr "协议是必填的: {}" @@ -1660,11 +1665,7 @@ msgstr "启用账号收集" msgid "Gather accounts method" msgstr "收集账号方式" -#: assets/serializers/platform.py:77 -msgid "Primary" -msgstr "主要的" - -#: assets/serializers/platform.py:119 +#: assets/serializers/platform.py:118 msgid "Default Domain" msgstr "默认网域" @@ -3141,19 +3142,24 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: common/views/mixins.py:58 +#: common/views/mixins.py:73 msgid "Export all" msgstr "导出所有" -#: common/views/mixins.py:60 +#: common/views/mixins.py:75 msgid "Export only selected items" msgstr "仅导出选择项" -#: common/views/mixins.py:65 +#: common/views/mixins.py:80 #, python-format msgid "Export filtered: %s" msgstr "导出搜素: %s" +#: common/views/mixins.py:90 +#, python-format +msgid "User %s view/export secret" +msgstr "用户 %s 查看/导出 了密码" + #: jumpserver/conf.py:416 msgid "Create account successfully" msgstr "创建账号成功" @@ -3225,11 +3231,11 @@ msgstr "发布站内消息" msgid "No account available" msgstr "无可用账号" -#: ops/ansible/inventory.py:196 +#: ops/ansible/inventory.py:209 msgid "Ansible disabled" msgstr "Ansible 已禁用" -#: ops/ansible/inventory.py:212 +#: ops/ansible/inventory.py:225 msgid "Skip hosts below:" msgstr "跳过以下主机: " @@ -4872,42 +4878,38 @@ msgid "Remember manual auth" msgstr "保存手动输入密码" #: settings/serializers/security.py:172 -msgid "Enable change auth secure mode" -msgstr "启用改密安全模式" - -#: settings/serializers/security.py:175 msgid "Insecure command alert" msgstr "危险命令告警" -#: settings/serializers/security.py:178 +#: settings/serializers/security.py:175 msgid "Email recipient" msgstr "邮件收件人" -#: settings/serializers/security.py:179 +#: settings/serializers/security.py:176 msgid "Multiple user using , split" msgstr "多个用户,使用 , 分割" -#: settings/serializers/security.py:182 +#: settings/serializers/security.py:179 msgid "Operation center" msgstr "作业中心" -#: settings/serializers/security.py:183 +#: settings/serializers/security.py:180 msgid "Allow user run batch command or not using ansible" msgstr "是否允许用户使用 ansible 执行批量命令" -#: settings/serializers/security.py:186 +#: settings/serializers/security.py:183 msgid "Session share" msgstr "会话分享" -#: settings/serializers/security.py:187 +#: settings/serializers/security.py:184 msgid "Enabled, Allows user active session to be shared with other users" msgstr "开启后允许用户分享已连接的资产会话给他人,协同工作" -#: settings/serializers/security.py:190 +#: settings/serializers/security.py:187 msgid "Remote Login Protection" msgstr "异地登录保护" -#: settings/serializers/security.py:192 +#: settings/serializers/security.py:189 msgid "" "The system determines whether the login IP address belongs to a common login " "city. If the account is logged in from a common login city, the system sends " From 616e1ded20b719a0448e04a4d3b4e7668cb59df3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Mar 2023 22:33:27 +0000 Subject: [PATCH 044/100] build(deps): bump redis from 4.3.3 to 4.5.3 in /requirements Bumps [redis](https://github.com/redis/redis-py) from 4.3.3 to 4.5.3. - [Release notes](https://github.com/redis/redis-py/releases) - [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES) - [Commits](https://github.com/redis/redis-py/compare/v4.3.3...v4.5.3) --- updated-dependencies: - dependency-name: redis dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8ef8712ab..916a56c8d 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -121,7 +121,7 @@ django-mysql==3.9.0 django-redis==5.2.0 python-redis-lock==3.7.0 pyOpenSSL==22.0.0 -redis==4.3.3 +redis==4.5.3 pyOpenSSL==22.0.0 pymongo==4.2.0 # Debug From d04a0ff5d71d4c5ea0c59de240227a93c6b95cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Tue, 28 Mar 2023 11:11:15 +0800 Subject: [PATCH 045/100] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=20ssh=20=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile.loong64 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 180f4c90f..4cad1e0bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ && mkdir -p /root/.ssh/ \ - && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \ + && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index dbffcc7a6..629623a85 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -53,7 +53,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ && mkdir -p /root/.ssh/ \ - && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \ + && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ From 3fa1b463125677d60c591e6937617faa6a9b187d Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 28 Mar 2023 11:37:04 +0800 Subject: [PATCH 046/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E8=A7=84=E5=88=99Util=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index d3d1562c0..b9b5b01be 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -92,7 +92,7 @@ class AssetPermissionUtil(object): @staticmethod def convert_to_queryset_if_need(objs_or_ids, model): if not objs_or_ids: - return objs_or_ids + return model.objects.none() if isinstance(objs_or_ids, QuerySet) and isinstance(objs_or_ids.first(), model): return objs_or_ids ids = [ From 23b13db9e25dc7ebe878b99010e083a1185031c8 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 28 Mar 2023 15:04:24 +0800 Subject: [PATCH 047/100] perf: category order (#10087) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/const/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 00a496eee..3f2335ceb 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -136,8 +136,8 @@ class AllTypes(ChoicesMixin): (Category.HOST, HostTypes), (Category.DEVICE, DeviceTypes), (Category.DATABASE, DatabaseTypes), + (Category.CLOUD, CloudTypes), (Category.WEB, WebTypes), - (Category.CLOUD, CloudTypes) ) @classmethod From 8d3319717edf6ba00758199d5884ee790a4d583a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:26:40 +0800 Subject: [PATCH 048/100] =?UTF-8?q?perf:=20=E5=BC=80=E5=90=AF=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E6=A8=A1=E5=BC=8F=E5=90=8E=E8=BF=87=E6=BB=A4root=20ad?= =?UTF-8?q?ministrator=20(#10089)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/automations/change_secret/manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py index 999fc2c75..7bad9e5f6 100644 --- a/apps/accounts/automations/change_secret/manager.py +++ b/apps/accounts/automations/change_secret/manager.py @@ -78,7 +78,9 @@ class ChangeSecretManager(AccountBasePlaybookManager): accounts = accounts.filter(secret_type=self.secret_type) if settings.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED: - accounts = accounts.filter(privileged=False) + accounts = accounts.filter(privileged=False).exclude( + username__in=['root', 'administrator'] + ) return accounts def host_callback( From 5b017daba144a9aae1a61eca66b0f31977655d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Wed, 29 Mar 2023 10:03:14 +0800 Subject: [PATCH 049/100] =?UTF-8?q?perf:=20=E4=BD=BF=E7=94=A8=20docker.io?= =?UTF-8?q?=20=E4=BB=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile-ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile-ee b/Dockerfile-ee index dc2ae308e..63c65d21c 100644 --- a/Dockerfile-ee +++ b/Dockerfile-ee @@ -1,6 +1,6 @@ ARG VERSION FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack -FROM ghcr.io/jumpserver/core:${VERSION} +FROM jumpserver/core:${VERSION} COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack WORKDIR /opt/jumpserver From 678df5bf3ec707a7bf1408746bfbb5dd01076225 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 29 Mar 2023 13:48:51 +0800 Subject: [PATCH 050/100] =?UTF-8?q?perf:=20=E8=A1=A5=E5=9B=9Eget=5Ftermina?= =?UTF-8?q?l=5Flatest=5Fstat=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/models/component/status.py | 38 +++++++++++++++++++++- apps/terminal/models/component/terminal.py | 5 +++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/terminal/models/component/status.py b/apps/terminal/models/component/status.py index 3da13d9f0..279d6ac95 100644 --- a/apps/terminal/models/component/status.py +++ b/apps/terminal/models/component/status.py @@ -1,11 +1,12 @@ import uuid +from django.core.cache import cache from django.db import models +from django.forms.models import model_to_dict from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger - logger = get_logger(__name__) @@ -21,9 +22,44 @@ class Status(models.Model): terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.CASCADE) date_created = models.DateTimeField(auto_now_add=True) + CACHE_KEY = 'TERMINAL_STATUS_{}' + class Meta: db_table = 'terminal_status' get_latest_by = 'date_created' verbose_name = _("Status") + @classmethod + def get_terminal_latest_stat(cls, terminal): + key = cls.CACHE_KEY.format(terminal.id) + data = cache.get(key) + if not data: + return None + data.pop('terminal', None) + stat = cls(**data) + stat.terminal = terminal + stat.is_alive = terminal.is_alive + stat.keep_one_decimal_place() + return stat + def keep_one_decimal_place(self): + keys = ['cpu_load', 'memory_used', 'disk_used'] + for key in keys: + value = getattr(self, key, 0) + if not isinstance(value, (int, float)): + continue + value = '%.1f' % value + setattr(self, key, float(value)) + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None): + self.terminal.set_alive(ttl=120) + return self.save_to_cache() + + def save_to_cache(self): + if not self.terminal: + return + key = self.CACHE_KEY.format(self.terminal.id) + data = model_to_dict(self) + cache.set(key, data, 60 * 3) + return data diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index 406d2192a..df09df680 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,6 +1,7 @@ import time from django.conf import settings +from django.core.cache import cache from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -35,6 +36,10 @@ class TerminalStatusMixin: return False return time.time() - self.last_stat.date_created.timestamp() < 150 + def set_alive(self, ttl=120): + key = self.ALIVE_KEY.format(self.id) + cache.set(key, True, ttl) + class StorageMixin: command_storage: str From 344451ba553793d93bdc45a62289f189bbc8844c Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 29 Mar 2023 14:28:00 +0800 Subject: [PATCH 051/100] =?UTF-8?q?perf:=20navicat=20=E7=A7=BB=E5=88=B0?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/applets/navicat/README.md | 4 - apps/terminal/applets/navicat/app.py | 411 ------------------ apps/terminal/applets/navicat/common.py | 215 --------- apps/terminal/applets/navicat/const.py | 8 - apps/terminal/applets/navicat/i18n.yml | 3 - apps/terminal/applets/navicat/icon.png | Bin 5132 -> 0 bytes apps/terminal/applets/navicat/main.py | 22 - apps/terminal/applets/navicat/manifest.yml | 17 - apps/terminal/applets/navicat/setup.yml | 6 - .../deploy_applet_host/playbook.yml | 13 - 10 files changed, 699 deletions(-) delete mode 100644 apps/terminal/applets/navicat/README.md delete mode 100644 apps/terminal/applets/navicat/app.py delete mode 100644 apps/terminal/applets/navicat/common.py delete mode 100644 apps/terminal/applets/navicat/const.py delete mode 100644 apps/terminal/applets/navicat/i18n.yml delete mode 100644 apps/terminal/applets/navicat/icon.png delete mode 100644 apps/terminal/applets/navicat/main.py delete mode 100644 apps/terminal/applets/navicat/manifest.yml delete mode 100644 apps/terminal/applets/navicat/setup.yml diff --git a/apps/terminal/applets/navicat/README.md b/apps/terminal/applets/navicat/README.md deleted file mode 100644 index ee18cd491..000000000 --- a/apps/terminal/applets/navicat/README.md +++ /dev/null @@ -1,4 +0,0 @@ -## Navicat Premium - -- 需要先手动导入License激活 - diff --git a/apps/terminal/applets/navicat/app.py b/apps/terminal/applets/navicat/app.py deleted file mode 100644 index 7c2dfd3bc..000000000 --- a/apps/terminal/applets/navicat/app.py +++ /dev/null @@ -1,411 +0,0 @@ -import os -import shutil -import time - -import winreg -import win32api -import win32con - -import const as c - -from pywinauto import Application -from pywinauto.keyboard import send_keys -from pywinauto.controls.uia_controls import ( - EditWrapper, ComboBoxWrapper, ButtonWrapper -) - -from common import wait_pid, BaseApplication, _messageBox - -_default_path = r'C:\Program Files\PremiumSoft\Navicat Premium 16\navicat.exe' - - -class AppletApplication(BaseApplication): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.path = _default_path - self.username = self.account.username - self.password = self.account.secret - self.privileged = self.account.privileged - self.host = self.asset.address - self.port = self.asset.get_protocol_port(self.protocol) - self.db = self.asset.spec_info.db_name - self.name = '%s-%s-%s' % (self.host, self.db, int(time.time())) - self.use_ssl = self.asset.spec_info.use_ssl - self.client_key = self.asset.secret_info.client_key - self.client_key_path = None - self.pid = None - self.app = None - - @staticmethod - def get_cert_path(): - win_user_name = win32api.GetUserName() - cert_path = r'C:\Users\%s\AppData\Roaming\Navicat\certs' % win_user_name - return cert_path - - def clean_up(self): - protocols = ( - 'NavicatMARIADB', 'NavicatMONGODB', 'Navicat', - 'NavicatORA', 'NavicatMSSQL', 'NavicatPG' - ) - for p in protocols: - sub_key = r'Software\PremiumSoft\%s\Servers' % p - try: - win32api.RegDeleteTree(winreg.HKEY_CURRENT_USER, sub_key) - except Exception: - pass - cert_path = self.get_cert_path() - shutil.rmtree(cert_path, ignore_errors=True) - - def gen_asset_file(self): - if self.use_ssl and self.client_key: - cert_path = self.get_cert_path() - if not os.path.exists(cert_path): - os.makedirs(cert_path, exist_ok=True) - filepath = os.path.join(cert_path, str(int(time.time()))) - with open(filepath, 'w') as f: - f.write(self.client_key) - self.client_key_path = filepath - - @staticmethod - def edit_regedit(): - sub_key = r'Software\PremiumSoft\NavicatPremium' - try: - key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, sub_key) - # 禁止弹出欢迎页面 - winreg.SetValueEx(key, 'AlreadyShowNavicatV16WelcomeScreen', 0, winreg.REG_DWORD, 1) - # 禁止开启自动检查更新 - winreg.SetValueEx(key, 'AutoCheckUpdate', 0, winreg.REG_DWORD, 0) - # 禁止弹出初始化界面 - winreg.SetValueEx(key, 'ShareUsageData', 0, winreg.REG_DWORD, 0) - except Exception as err: - print('Launch error: %s' % err) - - def launch(self): - # 清理因为异常未关闭的会话历史记录 - self.clean_up() - # 生成资产依赖的相关文件 - self.gen_asset_file() - # 修改注册表,达到一些配置目的 - self.edit_regedit() - - @staticmethod - def _exec_commands(commands): - for command in commands: - pre_check = command.get('pre_check', lambda: True) - if not pre_check(): - _messageBox('程序启动异常,请重新连接!!', 'Error', win32con.MB_DEFAULT_DESKTOP_ONLY) - return - - time.sleep(0.5) - if command['type'] == 'key': - send_keys(' '.join(command['commands'])) - elif command['type'] == 'action': - for f in command['commands']: - f() - - def _pre_check_is_password_input(self): - try: - self.app.window(best_match='Connection Password') - except Exception: - return False - return True - - def _action_ele_click(self, ele_name, conn_win=None): - if not conn_win: - conn_win = self.app.window(best_match='Dialog'). \ - child_window(title_re='New Connection') - conn_win.child_window(best_match=ele_name).click() - - def _fill_mysql_auth_info(self): - conn_window = self.app.window(best_match='Dialog'). \ - child_window(title_re='New Connection') - - name_ele = conn_window.child_window(best_match='Edit5') - EditWrapper(name_ele.element_info).set_edit_text(self.name) - - host_ele = conn_window.child_window(best_match='Edit4') - EditWrapper(host_ele.element_info).set_edit_text(self.host) - - port_ele = conn_window.child_window(best_match='Edit2') - EditWrapper(port_ele.element_info).set_edit_text(self.port) - - username_ele = conn_window.child_window(best_match='Edit1') - EditWrapper(username_ele.element_info).set_edit_text(self.username) - - def _get_mysql_commands(self): - commands = [ - { - 'type': 'key', - 'commands': [ - '%f', c.DOWN, c.RIGHT, c.ENTER - ], - }, - { - 'type': 'action', - 'commands': [ - self._fill_mysql_auth_info, lambda: self._action_ele_click('Save password') - ] - }, - { - 'type': 'key', - 'commands': [c.ENTER] - } - ] - return commands - - def _get_mariadb_commands(self): - commands = [ - { - 'type': 'key', - 'commands': [ - '%f', c.DOWN, c.RIGHT, c.DOWN * 5, c.ENTER, - ], - }, - { - 'type': 'action', - 'commands': [ - self._fill_mysql_auth_info, lambda: self._action_ele_click('Save password') - ] - }, - { - 'type': 'key', - 'commands': [c.ENTER] - } - ] - return commands - - def _fill_mongodb_auth_info(self): - conn_window = self.app.window(best_match='Dialog'). \ - child_window(title_re='New Connection') - - auth_type_ele = conn_window.child_window(best_match='ComboBox2') - ComboBoxWrapper(auth_type_ele.element_info).select('Password') - - name_ele = conn_window.child_window(best_match='Edit5') - EditWrapper(name_ele.element_info).set_edit_text(self.name) - - host_ele = conn_window.child_window(best_match='Edit4') - EditWrapper(host_ele.element_info).set_edit_text(self.host) - - port_ele = conn_window.child_window(best_match='Edit2') - EditWrapper(port_ele.element_info).set_edit_text(self.port) - - db_ele = conn_window.child_window(best_match='Edit6') - EditWrapper(db_ele.element_info).set_edit_text(self.db) - - username_ele = conn_window.child_window(best_match='Edit1') - EditWrapper(username_ele.element_info).set_edit_text(self.username) - - def _get_mongodb_commands(self): - commands = [ - { - 'type': 'key', - 'commands': [ - '%f', c.DOWN, c.RIGHT, c.DOWN * 6, c.ENTER, - ], - }, - { - 'type': 'action', - 'commands': [ - self._fill_mongodb_auth_info, lambda: self._action_ele_click('Save password') - ] - }, - { - 'type': 'key', - 'commands': [c.ENTER] - } - ] - if self.use_ssl: - ssl_commands = [ - { - 'type': 'key', - 'commands': [c.TAB * 5, c.RIGHT * 3, c.TAB] - }, - { - 'type': 'action', - 'commands': [ - lambda: self._action_ele_click('Use SSL'), - lambda: self._action_ele_click('Use authentication'), - ] - }, - { - 'type': 'key', - 'commands': [c.TAB, self.client_key_path] - }, - { - 'type': 'action', - 'commands': [lambda: self._action_ele_click('Allow invalid host names')] - } - ] - commands = commands[:2] + ssl_commands + commands[2:] - return commands - - def _fill_postgresql_auth_info(self): - conn_window = self.app.window(best_match='Dialog'). \ - child_window(title_re='New Connection') - - name_ele = conn_window.child_window(best_match='Edit6') - EditWrapper(name_ele.element_info).set_edit_text(self.name) - - host_ele = conn_window.child_window(best_match='Edit5') - EditWrapper(host_ele.element_info).set_edit_text(self.host) - - port_ele = conn_window.child_window(best_match='Edit2') - EditWrapper(port_ele.element_info).set_edit_text(self.port) - - db_ele = conn_window.child_window(best_match='Edit4') - EditWrapper(db_ele.element_info).set_edit_text(self.db) - - username_ele = conn_window.child_window(best_match='Edit1') - EditWrapper(username_ele.element_info).set_edit_text(self.username) - - def _get_postgresql_commands(self): - commands = [ - { - 'type': 'key', - 'commands': [ - '%f', c.DOWN, c.RIGHT, c.DOWN, c.ENTER, - ], - }, - { - 'type': 'action', - 'commands': [ - self._fill_postgresql_auth_info, lambda: self._action_ele_click('Save password') - ] - }, - { - 'type': 'key', - 'commands': [c.ENTER] - } - ] - return commands - - def _fill_sqlserver_auth_info(self): - conn_window = self.app.window(best_match='Dialog'). \ - child_window(title_re='New Connection') - - name_ele = conn_window.child_window(best_match='Edit5') - EditWrapper(name_ele.element_info).set_edit_text(self.name) - - host_ele = conn_window.child_window(best_match='Edit4') - EditWrapper(host_ele.element_info).set_edit_text('%s,%s' % (self.host, self.port)) - - db_ele = conn_window.child_window(best_match='Edit3') - EditWrapper(db_ele.element_info).set_edit_text(self.db) - - username_ele = conn_window.child_window(best_match='Edit6') - EditWrapper(username_ele.element_info).set_edit_text(self.username) - - def _get_sqlserver_commands(self): - commands = [ - { - 'type': 'key', - 'commands': [ - '%f', c.DOWN, c.RIGHT, c.DOWN * 4, c.ENTER, - ], - }, - { - 'type': 'action', - 'commands': [ - self._fill_sqlserver_auth_info, lambda: self._action_ele_click('Save password') - ] - }, - { - 'type': 'key', - 'commands': [c.ENTER] - } - ] - return commands - - def _fill_oracle_auth_info(self): - conn_window = self.app.window(best_match='Dialog'). \ - child_window(title_re='New Connection') - - name_ele = conn_window.child_window(best_match='Edit6') - EditWrapper(name_ele.element_info).set_edit_text(self.name) - - host_ele = conn_window.child_window(best_match='Edit5') - EditWrapper(host_ele.element_info).set_edit_text(self.host) - - port_ele = conn_window.child_window(best_match='Edit3') - EditWrapper(port_ele.element_info).set_edit_text(self.port) - - db_ele = conn_window.child_window(best_match='Edit2') - EditWrapper(db_ele.element_info).set_edit_text(self.db) - - username_ele = conn_window.child_window(best_match='Edit') - EditWrapper(username_ele.element_info).set_edit_text(self.username) - - if self.privileged: - conn_window.child_window(best_match='Advanced', control_type='TabItem').click_input() - role_ele = conn_window.child_window(best_match='ComboBox2') - ComboBoxWrapper(role_ele.element_info).select('SYSDBA') - - def _get_oracle_commands(self): - commands = [ - { - 'type': 'key', - 'commands': [ - '%f', c.DOWN, c.RIGHT, c.DOWN * 2, c.ENTER, - ], - }, - { - 'type': 'action', - 'commands': [ - lambda: self._action_ele_click('Save password'), self._fill_oracle_auth_info - ] - }, - { - 'type': 'key', - 'commands': [c.ENTER] - } - ] - return commands - - def run(self): - self.launch() - self.app = Application(backend='uia') - work_dir = os.path.dirname(self.path) - self.app.start(self.path, work_dir=work_dir) - self.pid = self.app.process - - # 检测是否为试用版本 - try: - trial_btn = self.app.top_window().child_window( - best_match='Trial', control_type='Button' - ) - ButtonWrapper(trial_btn.element_info).click() - time.sleep(0.5) - except Exception: - pass - - # 根据协议获取相应操作命令 - action = getattr(self, '_get_%s_commands' % self.protocol, None) - if action is None: - raise ValueError('This protocol is not supported: %s' % self.protocol) - commands = action() - # 关闭掉桌面许可弹框 - commands.insert(0, {'type': 'key', 'commands': (c.ESC,)}) - # 登录 - commands.extend([ - { - 'type': 'key', - 'commands': ( - '%f', c.DOWN * 5, c.ENTER - ) - }, - { - 'type': 'key', - 'commands': (self.password, c.ENTER), - 'pre_check': self._pre_check_is_password_input - } - ]) - self._exec_commands(commands) - - def wait(self): - try: - wait_pid(self.pid) - except Exception: - pass - finally: - self.clean_up() diff --git a/apps/terminal/applets/navicat/common.py b/apps/terminal/applets/navicat/common.py deleted file mode 100644 index 62becd792..000000000 --- a/apps/terminal/applets/navicat/common.py +++ /dev/null @@ -1,215 +0,0 @@ -import abc -import base64 -import json -import locale -import os -import subprocess -import sys -import time -from subprocess import CREATE_NO_WINDOW - -_blockInput = None -_messageBox = None -if sys.platform == 'win32': - import ctypes - from ctypes import wintypes - import win32ui - - # import win32con - - _messageBox = win32ui.MessageBox - - _blockInput = ctypes.windll.user32.BlockInput - _blockInput.argtypes = [wintypes.BOOL] - _blockInput.restype = wintypes.BOOL - - -def block_input(): - if _blockInput: - _blockInput(True) - - -def unblock_input(): - if _blockInput: - _blockInput(False) - - -def decode_content(content: bytes) -> str: - for encoding_name in ['utf-8', 'gbk', 'gb2312']: - try: - return content.decode(encoding_name) - except Exception as e: - print(e) - encoding_name = locale.getpreferredencoding() - return content.decode(encoding_name) - - -def notify_err_message(msg): - if _messageBox: - _messageBox(msg, 'Error') - - -def check_pid_alive(pid) -> bool: - # tasklist /fi "PID eq 508" /fo csv - # '"映像名称","PID","会话名 ","会话# ","内存使用 "\r\n"wininit.exe","508","Services","0","6,920 K"\r\n' - try: - - csv_ret = subprocess.check_output(["tasklist", "/fi", f'PID eq {pid}', "/fo", "csv"], - creationflags=CREATE_NO_WINDOW) - content = decode_content(csv_ret) - content_list = content.strip().split("\r\n") - if len(content_list) != 2: - print("check pid {} ret invalid: {}".format(pid, content)) - return False - ret_pid = content_list[1].split(",")[1].strip('"') - return str(pid) == ret_pid - except Exception as e: - print("check pid {} err: {}".format(pid, e)) - return False - - -def wait_pid(pid): - while 1: - time.sleep(5) - ok = check_pid_alive(pid) - if not ok: - print("pid {} is not alive".format(pid)) - break - - -class DictObj: - def __init__(self, in_dict: dict): - assert isinstance(in_dict, dict) - for key, val in in_dict.items(): - if isinstance(val, (list, tuple)): - setattr(self, key, [DictObj(x) if isinstance(x, dict) else x for x in val]) - else: - setattr(self, key, DictObj(val) if isinstance(val, dict) else val) - - -class User(DictObj): - id: str - name: str - username: str - - -class Specific(DictObj): - # web - autofill: str - username_selector: str - password_selector: str - submit_selector: str - script: list - - # database - db_name: str - use_ssl: str - - -class Secret(DictObj): - client_key: str - - -class Category(DictObj): - value: str - label: str - - -class Protocol(DictObj): - id: str - name: str - port: int - - -class Asset(DictObj): - id: str - name: str - address: str - protocols: list[Protocol] - category: Category - spec_info: Specific - secret_info: Secret - - def get_protocol_port(self, protocol): - for item in self.protocols: - if item.name == protocol: - return item.port - return None - - -class LabelValue(DictObj): - label: str - value: str - - -class Account(DictObj): - id: str - name: str - username: str - secret: str - privileged: bool - secret_type: LabelValue - - -class Platform(DictObj): - charset: str - name: str - charset: LabelValue - type: LabelValue - - -class Manifest(DictObj): - name: str - version: str - path: str - exec_type: str - connect_type: str - protocols: list[str] - - -def get_manifest_data() -> dict: - current_dir = os.path.dirname(__file__) - manifest_file = os.path.join(current_dir, 'manifest.json') - try: - with open(manifest_file, "r", encoding='utf8') as f: - return json.load(f) - except Exception as e: - print(e) - return {} - - -def read_app_manifest(app_dir) -> dict: - main_json_file = os.path.join(app_dir, "manifest.json") - if not os.path.exists(main_json_file): - return {} - with open(main_json_file, 'r', encoding='utf8') as f: - return json.load(f) - - -def convert_base64_to_dict(base64_str: str) -> dict: - try: - data_json = base64.decodebytes(base64_str.encode('utf-8')).decode('utf-8') - return json.loads(data_json) - except Exception as e: - print(e) - return {} - - -class BaseApplication(abc.ABC): - - def __init__(self, *args, **kwargs): - self.app_name = kwargs.get('app_name', '') - self.protocol = kwargs.get('protocol', '') - self.manifest = Manifest(kwargs.get('manifest', {})) - self.user = User(kwargs.get('user', {})) - self.asset = Asset(kwargs.get('asset', {})) - self.account = Account(kwargs.get('account', {})) - self.platform = Platform(kwargs.get('platform', {})) - - @abc.abstractmethod - def run(self): - raise NotImplementedError('run') - - @abc.abstractmethod - def wait(self): - raise NotImplementedError('wait') diff --git a/apps/terminal/applets/navicat/const.py b/apps/terminal/applets/navicat/const.py deleted file mode 100644 index 7c186b8cc..000000000 --- a/apps/terminal/applets/navicat/const.py +++ /dev/null @@ -1,8 +0,0 @@ - -UP = '{UP}' -LEFT = '{LEFT}' -DOWN = '{DOWN}' -RIGHT = '{RIGHT}' -TAB = '{VK_TAB}' -ESC = '{ESC}' -ENTER = '{VK_RETURN}' diff --git a/apps/terminal/applets/navicat/i18n.yml b/apps/terminal/applets/navicat/i18n.yml deleted file mode 100644 index ec6427048..000000000 --- a/apps/terminal/applets/navicat/i18n.yml +++ /dev/null @@ -1,3 +0,0 @@ -- zh: - display_name: Navicat premium 16 - comment: 数据库管理软件 diff --git a/apps/terminal/applets/navicat/icon.png b/apps/terminal/applets/navicat/icon.png deleted file mode 100644 index 10b343bf0144f6b88f9d36eeb86db264eca67579..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5132 zcmZ`-XE5B4^Zw|Eh#ECqMDM*u4Wbi4L>JNP;dIX7LPQM_qL+w^C{Zp-bfWiDqD1f9 z>0S8wzWl%V&(7{MyF1U!&c4{$-I(Wk>SQF0Bme-AX=LIl;+RU_KKA;u%;(B%5y1pw3(nySi10W%nLXDH3Y&l^4CldK^S zg9a|)({_rFAZ}$s2D<=~*R*PNd2s#F@9Vazt$g<%MHDRa<3>PO=?!Q;3p&w&3_wV@ z)p}eKf;~GQZRneQ1T*q~VQEL(Svx=LYV6|MusuG%kmH|Cc#}w7pRjWX;17SsNdoF{ zo)2%d;9I%^0YKEp&)LgSj+qnEuovA8PVi0hqGJdFM(1 zR7^l_c0O?=#O!TKiS<~cb5}s*0HP=HAcx0Zf)ZcPdhwKiVF zTcuFRUKOXug_u0ne&0}y3*b^*kh@84jjKD$oV7Hs@AC9uN6ngVlOCXG0bHe8H>=v_ z4kCmgu{h~JB@n25vW|$zT1jqdCi{4S z3tZl4fBcI8e(s3VN^s$dE7dX`Y~>b=Rt(5-awRY%Egz9q&R*2|@xI@}`nb_?K^&0K zl`}b-kF7lgOK^vn!cEacs!>MJ>al8>Nn@3-4viS7X6hh*)u(e>l3_(*PTA9rG>_?r z4?NQQybxTfljVCj!i0{Wf1{*stKVyDJJ_qe1e$A~6agTt` zVnXJHd{!`>+RF@cC+q6<>MVW9(9~QanHg%}Yql#kK)lj?vHx(*7wh${vr@$CxY-oQ zIyv~$GE$Z@Z-rBQdhLlyyESIbroyzoGT%G9d$t**>#U>plfMD=^lDGmO5q13i>vPNap3YV@QOhr)S&8#aJ znqg(}aZ3`E%>?K|7SiuCZ77bFnH!2qo^ou>ig7RJYceZLIA4H@x`|;c09^K>I9CRr9#|>AIQ1IG|m?n_YS`P!m zj-S0njvm9V?~WI+WOIt<45<@$W&qx3<4}yvZ;RDirr*Fk8x9Kkl`I$(KMlbkuu~JH zqKlMT@}Qe_mr|&a^;g1rEs+5xHR%7W=iui7;ZB5mXcs)sJA*7RqM3+PevPuWFA4--BW@HsQhUUU{jeh$)l2ZzxXb%<$Ki*+J?fXM1 zrIycPf+Pnlw>z?PCw9&uCmpfn(m&&JvM*Qxb-uSmfCH*Nwt~chv78Ku-(Y;>M%2RN zLhxr7TTKm?uOm7+yPxoca~Ia~qo-NTnE(M9*%5!;PdArCqBKPVu>0y+oFO3{;;2Mf zOK~Cl-WVfs%PL7?CAFegIObvta7~KM3yO$!9B*1Jv2ERP^}g=#NtTh;l;u&bWOBencUj>LD{xk zIi+-AG+5!HyvHtqLXTmBXVC(M)sbb~dpIq1bwna|pKkDsL{ARedeDe%t~}8HR@Tae z(b4r-U~MdeWtlzsL~<}o^a3^$?(iv1*T5JxTjYl`d<_SLYe>Zsj3$W@5bo-C44FTz z`JJM0=L*4d)rA9(9Vprhhp0f0RpM7zK7WFwjFEfs#ua8=S#n8+z{-mby8RBJ)0a0*9m%cq zb`!IX0h|oMSVqOrIbD6# zC?@O!eA^Nx#JCiNfuFZG*wV%8h6zq@GwGjpROestbkVza#)VaLm|wn8jLp5qcyW=a ztF;yA6Rz_-hXCq8U6Y8?tfZ=cbi*c31vE5lTc@b#Lo`HW^sA(LEqU0%w0eVs-#bwS zZcvt{{6ho(uQ94K=8ZnW#VD*Q)!4?_d*86YN%ixv#Zixt!aZa?T6XXLKKl5$jG>%%Zq1e> zb;9;ERdlL3;&J!noqlT6K9w--?q^{MS(t}azYrr=(yPScDW;YZEwTHu5G~n=y1c>a zyuYs7{4-ES3f`Mw_Dy}GqMCT^%|Xoz)Zh|lOYlhEn`gjG`X_ghW9Tx&x{0DX*hX+qLN`N(X1J% zi(JbmM8j!xD=sSp^sRC@gKOStd&B$z%UFhA3egrBQ%sL$33u4M#W*cir8hseLC(n2 zy5fVcqcOMu)oMljqY}{FKJoT}ae*a@<@S`c=`x<4zEY13@|6&Oc;-lDR9kGFSpg&y ztl?yO><0z^JXyn=?OHqDrfe9=;~Ld!YjO-%-`~Iau%o}56X28hVM_=P=}#Y2H21o8 z_V?42@I9mj8~bx=0yqSCm#YanVfLkb=MhBEoC?-#=HqqglV_gnrDn7?EWVepQp`)M z%Ha-{cr(BB7buZCtF3`L04{`ax3;>&l+K|E*A`hWwZBO0k5U2Nz#*Evk2_SQ z?Xb^w2-$T%)NNc{N1U@dAC382(R)_M2HN;$9Fx$}7BKmLVIGWGG!1YF!f?&YZ_#PXusWyr@_t!}#DtD5uzRL>fWdatfmvR9 zc-e_=lj3r#*&$zVj~;*c1{}W+CTLFw7t)Mx*d)^dW(ERQ`zRn{QOUhxgGK)xPOH0> zZ+Gh=U}^2kBE;_(BI$j3BjEH8iY!{SYQgMY8zGr$Oq#Sf&PUc(zr`X%bx;qc&#@mdH-$?trB zN4GS=6%`&i^NglA@^j!7V*`;!}+Kmy(2KH-DK#V{ae#N3uK@vMJ zW|04a7FYv*OqdUC(9+IM@kM4%U~kr9=n}HyNTdi6KSdId-v?Gdn=5|^?Z0Buw{DG} zUTyUzb?xhtOBR`Zv2{LLJMwyDpPk>=;hF@9>gPE1LA3|-=Y1Kde6!VEtRE}@09l0& zb_0q&m6t__S|-oh?5{HH_ihz>u@G|;0j$rjmmNd_#ijFyP?N4AvN{Fjis?t!K@A0~ zZ~gTLe>~}Yib|{{8;_f>y8daflG`_v7B>Ae;YA;J5we_8KO`w9g(NU*k6NJVDo8vj zmW~p*?Z=7;LF36R7BNF0R%E3n9WIsRF`G!ZMi7~Gxr+?0<-#Y}Y~Tq6BUIE-=@_+r zAtfx2z9Xiu$*8(*XT^$7cvIv0l8(9?2h!YlIUwYdb`z90wtYqW{aeEOsT~Q*?bDj7 znNY?lCxw9co@Tp~ts=c*BdyeWE8BPRl6r5mXc@L+OmuPcCP3kR2coPGOC`V2O9l9( zyni)=jcD`rMyE3=KWv5S z*!qSiiE29iGpUMWcc z4BnttpIc6G^omK4&3J@_j^sJa-8xd$9`>H!$c39)l0Q_m3@t$cKcZiV!BEqdcf;p- zLRv5fLJl-+cOvxkYmV1?bZUg5)@y$af(lqha5L(zTGW1(@2?<$lJN_3WsqhIY|vsQ zD8+AO8EY6Zcx}1;v-ZneJvAJ4*SC(w^yZy*u4Ho=<9wjd;xMVlq>xRAwXTvu`Rb+C zT#tg~Am2p0Hr0bOyVS6_XHgP^IBHlW6bOO>PX#BO({M{=emJlv2} zsKI$HL1pbM^w@ZmSbVg)rpKQkoV4!8MoyVKE~i zZnWOCWXwU7FbHqf|G|l2HS8vj2XUk71apQeXAZhy?7wkF{sy|*jGOEG!j6|#{lISt zU9hZhJpt*$d-v%C&SlY}_DkE1aqFNxwV%Iri19Ep-Q87d&CM54B0Ge6AbuPm!Kxa~_3YihOa;Hf63SNHMel$KEH8yUqo@|OI* zuF|R;e<3hKZ$IF+9N*@^%H5incq&Eg1tSz}(;r?KpmaDkt!8QCu`kbhyfIt-DL1UB zX6)~-e18}X04&O?eSSLj>CAMF_35p!(NSF#y$6W>1ZSphF;2v2_DkVa_4k}Wm?exe zWBwa#5NChB;?`zV!2dpmx~kJ{l{%BAK_cS2VOC_$t$xnG6H`7xH$Uytk|MK~Pe4En z7b3m3IU{|psc^v}jBO!Pn6#jZI`)qWV+8tq z34o{=mG;M+_dGIb7{4_AMsis~ah;2oH0itB65(5|O6)PRb5zwbYG%6-t+x_>TTp}g z0Jpsl?{mkR1q2Xs5uSrqkxYc}ONKtMA!$;vrrdTA<(?nIC>xR~zPSIk*j4QouPM^* z Date: Wed, 29 Mar 2023 15:17:55 +0800 Subject: [PATCH 052/100] =?UTF-8?q?perf:=20issues=20=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/----.md | 5 ++++- .github/ISSUE_TEMPLATE/bug---.md | 4 +++- .github/ISSUE_TEMPLATE/question.md | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/----.md b/.github/ISSUE_TEMPLATE/----.md index b407e8a25..147f42db4 100644 --- a/.github/ISSUE_TEMPLATE/----.md +++ b/.github/ISSUE_TEMPLATE/----.md @@ -3,7 +3,10 @@ name: 需求建议 about: 提出针对本项目的想法和建议 title: "[Feature] " labels: 类型:需求 -assignees: ibuler +assignees: + - ibuler + - baijiangjie + --- diff --git a/.github/ISSUE_TEMPLATE/bug---.md b/.github/ISSUE_TEMPLATE/bug---.md index 3c590459c..e4a21adde 100644 --- a/.github/ISSUE_TEMPLATE/bug---.md +++ b/.github/ISSUE_TEMPLATE/bug---.md @@ -3,7 +3,9 @@ name: Bug 提交 about: 提交产品缺陷帮助我们更好的改进 title: "[Bug] " labels: 类型:bug -assignees: wojiushixiaobai +assignees: + - wojiushixiaobai + - baijiangjie --- diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 1dd2a68d6..b15719590 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -3,7 +3,9 @@ name: 问题咨询 about: 提出针对本项目安装部署、使用及其他方面的相关问题 title: "[Question] " labels: 类型:提问 -assignees: wojiushixiaobai +assignees: + - wojiushixiaobai + - baijiangjie --- From cab72c69915acd3894811ef13d9e32bda06f6c68 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 29 Mar 2023 17:10:58 +0800 Subject: [PATCH 053/100] =?UTF-8?q?feat:=20Windows=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E5=A2=9E=E5=8A=A0winrm=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/host.py | 4 +- apps/assets/const/protocol.py | 8 +++ .../migrations/0111_auto_20230321_1633.py | 8 +++ apps/assets/serializers/platform.py | 3 + apps/ops/ansible/inventory.py | 56 ++++++++++++++++--- requirements/requirements.txt | 1 + 6 files changed, 69 insertions(+), 11 deletions(-) diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 7b6c8818c..afb92a447 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -36,7 +36,7 @@ class HostTypes(BaseType): 'choices': ['ssh', 'telnet', 'vnc', 'rdp'] }, cls.WINDOWS: { - 'choices': ['rdp', 'ssh', 'vnc'] + 'choices': ['rdp', 'ssh', 'vnc', 'winrm'] } } @@ -58,7 +58,7 @@ class HostTypes(BaseType): cls.WINDOWS: { 'ansible_config': { 'ansible_shell_type': 'cmd', - 'ansible_connection': 'ssh', + 'ansible_connection': 'smart', }, }, cls.OTHER_HOST: { diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index 6523c5dcc..45dbc10ec 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -10,6 +10,7 @@ class Protocol(ChoicesMixin, models.TextChoices): rdp = 'rdp', 'RDP' telnet = 'telnet', 'Telnet' vnc = 'vnc', 'VNC' + winrm = 'winrm', 'WinRM' mysql = 'mysql', 'MySQL' mariadb = 'mariadb', 'MariaDB' @@ -51,6 +52,13 @@ class Protocol(ChoicesMixin, models.TextChoices): 'port': 23, 'secret_types': ['password'], }, + cls.winrm: { + 'port': 5985, + 'secret_types': ['password'], + 'setting': { + 'use_ssl': False, + } + }, } @classmethod diff --git a/apps/assets/migrations/0111_auto_20230321_1633.py b/apps/assets/migrations/0111_auto_20230321_1633.py index 314d2ed7f..fce00cc98 100644 --- a/apps/assets/migrations/0111_auto_20230321_1633.py +++ b/apps/assets/migrations/0111_auto_20230321_1633.py @@ -2,6 +2,8 @@ from django.db import migrations, models +from assets.const import AllTypes + def migrate_platform_charset(apps, schema_editor): platform_model = apps.get_model('assets', 'Platform') @@ -20,6 +22,11 @@ def migrate_platform_protocol_primary(apps, schema_editor): p.save() +def migrate_internal_platforms(apps, schema_editor): + platform_cls = apps.get_model('assets', 'Platform') + AllTypes.create_or_update_internal_platforms(platform_cls) + + class Migration(migrations.Migration): dependencies = [ @@ -34,4 +41,5 @@ class Migration(migrations.Migration): ), migrations.RunPython(migrate_platform_charset), migrations.RunPython(migrate_platform_protocol_primary), + migrations.RunPython(migrate_internal_platforms), ] diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 53be193a6..5c458028d 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -41,6 +41,9 @@ class ProtocolSettingSerializer(serializers.Serializer): # Redis auth_username = serializers.BooleanField(default=False, label=_("Auth with username")) + # WinRM + use_ssl = serializers.BooleanField(default=False, label=_("Use SSL")) + class PlatformAutomationSerializer(serializers.ModelSerializer): class Meta: diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 6abf6d18e..e2b297b94 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -72,15 +72,13 @@ class JMSInventory: var['ansible_ssh_private_key_file'] = account.private_key_path return var - def make_ssh_account_vars(self, host, asset, account, automation, protocols, platform, gateway): + def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway): if not account: host['error'] = _("No account available") return host - ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) - ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None host['ansible_host'] = asset.address - host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 + host['ansible_port'] = protocol.port if protocol else 22 su_from = account.su_from if platform.su_enabled and su_from: @@ -110,6 +108,32 @@ class JMSInventory: port = 0 return protocol, port + @staticmethod + def get_ansible_protocol(ansible_config, protocols): + ansible_connection = ansible_config.get('ansible_connection') + if ansible_connection and not ansible_config == 'smart': + # 数值越小,优先级越高 + protocol_priority = {'ssh': 10, 'winrm': 9, ansible_connection: 1} + + protocol_matched = list(filter(lambda x: x.name in protocol_priority, protocols)) + protocol_sorted = sorted(protocol_matched, key=lambda x: protocol_priority[x.name]) + + protocol = protocol_sorted[0] if protocol_sorted else None + return protocol + + @staticmethod + def fill_ansible_config(ansible_config, protocol): + if protocol and protocol.name == 'winrm': + ansible_config['ansible_connection'] = 'winrm' + if protocol.setting.get('use_ssl', False): + ansible_config['ansible_winrm_scheme'] = 'https' + ansible_config['ansible_winrm_transport'] = 'ssl' + ansible_config['ansible_winrm_server_cert_validation'] = 'ignore' + else: + ansible_config['ansible_winrm_scheme'] = 'http' + ansible_config['ansible_winrm_transport'] = 'plaintext' + return ansible_config + def asset_to_host(self, asset, account, automation, protocols, platform): protocol, port = self.get_primary_protocol(protocols) @@ -133,15 +157,18 @@ class JMSInventory: try: ansible_config = dict(automation.ansible_config) - except Exception as e: + except (AttributeError, TypeError): ansible_config = {} - ansible_connection = ansible_config.get('ansible_connection', 'ssh') + + ansible_protocol = self.get_ansible_protocol(ansible_config, protocols) + ansible_config = self.fill_ansible_config(ansible_config, ansible_protocol) host.update(ansible_config) gateway = None if not asset.is_gateway and asset.domain: gateway = asset.domain.select_gateway() + ansible_connection = ansible_config.get('ansible_connection', 'ssh') if ansible_connection == 'local': if gateway: host['gateway'] = { @@ -149,7 +176,9 @@ class JMSInventory: 'username': gateway.username, 'secret': gateway.password } else: - self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway) + self.make_account_vars( + host, asset, account, automation, ansible_protocol, platform, gateway + ) return host def get_asset_sorted_accounts(self, asset): @@ -194,14 +223,23 @@ class JMSInventory: else: return None + @staticmethod + def _fill_attr_from_platform(asset, platform_protocols): + asset_protocols = asset.protocols.all() + for p in asset_protocols: + setattr(p, 'setting', platform_protocols.get(p.name, {})) + return asset_protocols + def generate(self, path_dir): hosts = [] platform_assets = self.group_by_platform(self.assets) for platform, assets in platform_assets.items(): automation = platform.automation - + platform_protocols = { + p['name']: p['setting'] for p in platform.protocols.values('name', 'setting') + } for asset in assets: - protocols = asset.protocols.all() + protocols = self._fill_attr_from_platform(asset, platform_protocols) account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols, platform) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8ef8712ab..612eb5112 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -65,6 +65,7 @@ pyjwkest==1.4.2 jsonfield2==4.0.0.post0 geoip2==4.5.0 ipip-ipdb==1.6.1 +pywinrm==0.4.3 # Django environment Django==3.2.17 django-bootstrap3==14.2.0 From caef6a5052ebc8e84f5ee2e316d62e03185261e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 30 Mar 2023 12:09:03 +0800 Subject: [PATCH 054/100] =?UTF-8?q?perf:=20applet=20=E4=BD=BF=E7=94=A8=20p?= =?UTF-8?q?owershell=20=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deploy_applet_host/install_all.yml | 5 ++-- .../deploy_applet_host/install_applet.yml | 5 ++-- .../deploy_applet_host/playbook.yml | 26 ++++++++++--------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/install_all.yml b/apps/terminal/automations/deploy_applet_host/install_all.yml index bf3da06b4..befa8f5a2 100644 --- a/apps/terminal/automations/deploy_applet_host/install_all.yml +++ b/apps/terminal/automations/deploy_applet_host/install_all.yml @@ -4,5 +4,6 @@ tasks: - name: Install all applets - ansible.windows.win_shell: - "tinkerd install all" + ansible.windows.win_powershell: + script: | + tinkerd install all diff --git a/apps/terminal/automations/deploy_applet_host/install_applet.yml b/apps/terminal/automations/deploy_applet_host/install_applet.yml index 5c216773f..de3c0fa49 100644 --- a/apps/terminal/automations/deploy_applet_host/install_applet.yml +++ b/apps/terminal/automations/deploy_applet_host/install_applet.yml @@ -6,6 +6,7 @@ tasks: - name: Install applet - ansible.windows.win_shell: - "tinkerd install --name {{ applet_name }}" + ansible.windows.win_powershell: + script: | + tinkerd install --name {{ applet_name }} when: applet_name != 'all' diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index bd819a93f..265428bcb 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -143,9 +143,9 @@ dest: "{{ ansible_env.TEMP }}\\pip_packages" - name: Install python requirements offline - ansible.windows.win_shell: > - pip install -r '{{ ansible_env.TEMP }}\pip_packages\pip_packages\requirements.txt' - --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages\pip_packages' + ansible.windows.win_powershell: + script: | + pip install -r '{{ ansible_env.TEMP }}\pip_packages\pip_packages\requirements.txt' --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages\pip_packages' - name: Download chromedriver (Chromium) ansible.windows.win_get_url: @@ -197,17 +197,18 @@ - /SILENT - name: Generate tinkerd component config - ansible.windows.win_shell: - "tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} - --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }} --ignore-verify-certs {{ IGNORE_VERIFY_CERTS }}" + ansible.windows.win_powershell: + tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }} --ignore-verify-certs {{ IGNORE_VERIFY_CERTS }} - name: Install tinkerd service - ansible.windows.win_shell: - "tinkerd service install" + ansible.windows.win_powershell: + script: | + tinkerd service install - name: Start tinkerd service - ansible.windows.win_shell: - "tinkerd service start" + ansible.windows.win_powershell: + script: | + tinkerd service start - name: Wait Tinker api health ansible.windows.win_uri: @@ -220,5 +221,6 @@ delay: 5 - name: Sync all remote applets - ansible.windows.win_shell: - "tinkerd install all" + ansible.windows.win_powershell: + script: | + tinkerd install all \ No newline at end of file From cfe0206179db696268dd3ed839bb79fb18c3ac11 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 30 Mar 2023 14:58:20 +0800 Subject: [PATCH 055/100] =?UTF-8?q?feat:=20winrm=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=BD=91=E5=9F=9F=E8=87=AA=E5=8A=A8=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 8 ++- apps/ops/ansible/inventory.py | 71 ++++++++++--------------- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 7184d91f7..d9d1cc73f 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -252,8 +252,12 @@ class BasePlaybookManager: print('\033[31m %s \033[0m\n' % err_msg) not_valid.append(k) else: - jms_asset['address'] = '127.0.0.1' - jms_asset['port'] = server.local_bind_port + if host['ansible_connection'] == 'winrm': + host['ansible_host'] = '127.0.0.1' + host['ansible_port'] = server.local_bind_port + else: + jms_asset['address'] = '127.0.0.1' + jms_asset['port'] = server.local_bind_port servers.append(server) # 网域不可连接的,就不继续执行此资源的后续任务了 diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index e2b297b94..e3bdaf899 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -77,8 +77,9 @@ class JMSInventory: host['error'] = _("No account available") return host + port = protocol.port if protocol else 22 host['ansible_host'] = asset.address - host['ansible_port'] = protocol.port if protocol else 22 + host['ansible_port'] = port su_from = account.su_from if platform.su_enabled and su_from: @@ -95,36 +96,31 @@ class JMSInventory: host.update(self.make_account_ansible_vars(account)) if gateway: - host.update(self.make_proxy_command(gateway)) - - @staticmethod - def get_primary_protocol(protocols): - if protocols: - primary = protocols[0] - protocol = primary.name - port = primary.port - else: - protocol = 'null' - port = 0 - return protocol, port + ansible_connection = host.get('ansible_connection', 'ssh') + if ansible_connection in ('local', 'winrm'): + host['gateway'] = { + 'address': gateway.address, 'port': gateway.port, + 'username': gateway.username, 'secret': gateway.password + } + host['jms_asset']['port'] = port + else: + host.update(self.make_proxy_command(gateway)) @staticmethod def get_ansible_protocol(ansible_config, protocols): + invalid_protocol = type('protocol', (), {'name': 'null', 'port': 0}) ansible_connection = ansible_config.get('ansible_connection') - if ansible_connection and not ansible_config == 'smart': - # 数值越小,优先级越高 - protocol_priority = {'ssh': 10, 'winrm': 9, ansible_connection: 1} - - protocol_matched = list(filter(lambda x: x.name in protocol_priority, protocols)) - protocol_sorted = sorted(protocol_matched, key=lambda x: protocol_priority[x.name]) - - protocol = protocol_sorted[0] if protocol_sorted else None + # 数值越小,优先级越高,若用户在 ansible_config 中配置了,则提高用户配置方式的优先级 + protocol_priority = {'ssh': 10, 'winrm': 9, ansible_connection: 1} + protocol_sorted = sorted(protocols, key=lambda x: protocol_priority.get(x.name, 999)) + protocol = protocol_sorted[0] if protocol_sorted else invalid_protocol return protocol @staticmethod def fill_ansible_config(ansible_config, protocol): + if protocol.name in ('ssh', 'winrm'): + ansible_config['ansible_connection'] = protocol.name if protocol and protocol.name == 'winrm': - ansible_config['ansible_connection'] = 'winrm' if protocol.setting.get('use_ssl', False): ansible_config['ansible_winrm_scheme'] = 'https' ansible_config['ansible_winrm_transport'] = 'ssl' @@ -135,14 +131,19 @@ class JMSInventory: return ansible_config def asset_to_host(self, asset, account, automation, protocols, platform): - protocol, port = self.get_primary_protocol(protocols) + try: + ansible_config = dict(automation.ansible_config) + except (AttributeError, TypeError): + ansible_config = {} + + protocol = self.get_ansible_protocol(ansible_config, protocols) host = { 'name': '{}'.format(asset.name.replace(' ', '_')), 'jms_asset': { 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, - 'protocol': protocol, 'port': port, + 'protocol': protocol.name, 'port': protocol.port, 'spec_info': asset.spec_info, 'secret_info': asset.secret_info, 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, @@ -155,30 +156,16 @@ class JMSInventory: if host['jms_account'] and asset.platform.type == 'oracle': host['jms_account']['mode'] = 'sysdba' if account.privileged else None - try: - ansible_config = dict(automation.ansible_config) - except (AttributeError, TypeError): - ansible_config = {} - - ansible_protocol = self.get_ansible_protocol(ansible_config, protocols) - ansible_config = self.fill_ansible_config(ansible_config, ansible_protocol) + ansible_config = self.fill_ansible_config(ansible_config, protocol) host.update(ansible_config) gateway = None if not asset.is_gateway and asset.domain: gateway = asset.domain.select_gateway() - ansible_connection = ansible_config.get('ansible_connection', 'ssh') - if ansible_connection == 'local': - if gateway: - host['gateway'] = { - 'address': gateway.address, 'port': gateway.port, - 'username': gateway.username, 'secret': gateway.password - } - else: - self.make_account_vars( - host, asset, account, automation, ansible_protocol, platform, gateway - ) + self.make_account_vars( + host, asset, account, automation, protocol, platform, gateway + ) return host def get_asset_sorted_accounts(self, asset): From 4c5e47cb99be16a548bbcbd87b3a5b94fe6bddef Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 30 Mar 2023 16:06:30 +0800 Subject: [PATCH 056/100] =?UTF-8?q?perf:=20=E6=99=AE=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=B7=A5=E5=8D=95=E7=94=B3=E8=AF=B7=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E6=8C=87=E5=AE=9A=E8=B4=A6=E5=8F=B7=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E6=97=A0=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/accounts/api/account/account.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py index 72e926d4a..0cfddd88c 100644 --- a/apps/accounts/api/account/account.py +++ b/apps/accounts/api/account/account.py @@ -8,7 +8,7 @@ from accounts import serializers from accounts.filters import AccountFilterSet from accounts.models import Account from assets.models import Asset, Node -from common.permissions import UserConfirmation, ConfirmType +from common.permissions import UserConfirmation, ConfirmType, IsValidUser from common.views.mixins import RecordViewLogMixin from orgs.mixins.api import OrgBulkModelViewSet from rbac.permissions import RBACPermission @@ -29,7 +29,6 @@ class AccountViewSet(OrgBulkModelViewSet): rbac_perms = { 'partial_update': ['accounts.change_account'], 'su_from_accounts': 'accounts.view_account', - 'username_suggestions': 'accounts.view_account', 'clear_secret': 'accounts.change_account', } @@ -50,7 +49,10 @@ class AccountViewSet(OrgBulkModelViewSet): serializer = serializers.AccountSerializer(accounts, many=True) return Response(data=serializer.data) - @action(methods=['get'], detail=False, url_path='username-suggestions') + @action( + methods=['get'], detail=False, url_path='username-suggestions', + permission_classes=[IsValidUser] + ) def username_suggestions(self, request, *args, **kwargs): asset_ids = request.query_params.get('assets') node_keys = request.query_params.get('keys') From 8b6a64d8ed8433e02d486bf3d05ba4d221b3582b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Mar 2023 14:31:35 +0000 Subject: [PATCH 057/100] build(deps): bump redis from 4.5.3 to 4.5.4 in /requirements Bumps [redis](https://github.com/redis/redis-py) from 4.5.3 to 4.5.4. - [Release notes](https://github.com/redis/redis-py/releases) - [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES) - [Commits](https://github.com/redis/redis-py/compare/v4.5.3...v4.5.4) --- updated-dependencies: - dependency-name: redis dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 916a56c8d..310bc9de6 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -121,7 +121,7 @@ django-mysql==3.9.0 django-redis==5.2.0 python-redis-lock==3.7.0 pyOpenSSL==22.0.0 -redis==4.5.3 +redis==4.5.4 pyOpenSSL==22.0.0 pymongo==4.2.0 # Debug From 6aaa20ba175531ebc332501209cf92eb757985b4 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 3 Apr 2023 09:57:40 +0800 Subject: [PATCH 058/100] =?UTF-8?q?Perf:=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 8 ++------ apps/ops/ansible/inventory.py | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index d9d1cc73f..1a9f0b8cc 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -252,12 +252,8 @@ class BasePlaybookManager: print('\033[31m %s \033[0m\n' % err_msg) not_valid.append(k) else: - if host['ansible_connection'] == 'winrm': - host['ansible_host'] = '127.0.0.1' - host['ansible_port'] = server.local_bind_port - else: - jms_asset['address'] = '127.0.0.1' - jms_asset['port'] = server.local_bind_port + host['ansible_host'] = jms_asset['address'] = '127.0.0.1' + host['ansible_port'] = jms_asset['port'] = server.local_bind_port servers.append(server) # 网域不可连接的,就不继续执行此资源的后续任务了 diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index e3bdaf899..b247d0547 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -120,7 +120,7 @@ class JMSInventory: def fill_ansible_config(ansible_config, protocol): if protocol.name in ('ssh', 'winrm'): ansible_config['ansible_connection'] = protocol.name - if protocol and protocol.name == 'winrm': + if protocol.name == 'winrm': if protocol.setting.get('use_ssl', False): ansible_config['ansible_winrm_scheme'] = 'https' ansible_config['ansible_winrm_transport'] = 'ssl' @@ -211,7 +211,7 @@ class JMSInventory: return None @staticmethod - def _fill_attr_from_platform(asset, platform_protocols): + def set_platform_protocol_setting_to_asset(asset, platform_protocols): asset_protocols = asset.protocols.all() for p in asset_protocols: setattr(p, 'setting', platform_protocols.get(p.name, {})) @@ -226,7 +226,7 @@ class JMSInventory: p['name']: p['setting'] for p in platform.protocols.values('name', 'setting') } for asset in assets: - protocols = self._fill_attr_from_platform(asset, platform_protocols) + protocols = self.set_platform_protocol_setting_to_asset(asset, platform_protocols) account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols, platform) From cbe384161ac4f51518b627432ca2a7fcef62e5d4 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 3 Apr 2023 10:17:00 +0800 Subject: [PATCH 059/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E5=87=BD=E6=95=B0=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index b247d0547..d98d105e5 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -107,7 +107,7 @@ class JMSInventory: host.update(self.make_proxy_command(gateway)) @staticmethod - def get_ansible_protocol(ansible_config, protocols): + def get_primary_protocol(ansible_config, protocols): invalid_protocol = type('protocol', (), {'name': 'null', 'port': 0}) ansible_connection = ansible_config.get('ansible_connection') # 数值越小,优先级越高,若用户在 ansible_config 中配置了,则提高用户配置方式的优先级 @@ -136,7 +136,7 @@ class JMSInventory: except (AttributeError, TypeError): ansible_config = {} - protocol = self.get_ansible_protocol(ansible_config, protocols) + protocol = self.get_primary_protocol(ansible_config, protocols) host = { 'name': '{}'.format(asset.name.replace(' ', '_')), From 789eb0cf365ba2588f1f9cfa51d9a4c70d7f2e94 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 3 Apr 2023 13:55:27 +0800 Subject: [PATCH 060/100] =?UTF-8?q?feat:=20=E5=8D=8F=E4=BD=9C=E5=88=86?= =?UTF-8?q?=E4=BA=AB=E5=A2=9E=E5=8A=A0=E8=AF=BB=E5=86=99=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/const.py | 7 ++++++- .../0060_sessionsharing_action_permission.py | 18 ++++++++++++++++ apps/terminal/models/session/sharing.py | 7 +++++++ apps/terminal/serializers/sharing.py | 21 ++++++++++++++----- 4 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 apps/terminal/migrations/0060_sessionsharing_action_permission.py diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 40adb4e82..20b82d775 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from django.db.models import TextChoices +from django.db.models import TextChoices, IntegerChoices from django.utils.translation import ugettext_lazy as _ @@ -65,3 +65,8 @@ class SessionType(TextChoices): normal = 'normal', _('Normal') tunnel = 'tunnel', _('Tunnel') command = 'command', _('Command') + + +class ActionPermission(IntegerChoices): + read_only = 0, _('Read Only') + writable = 1, _('Writable') diff --git a/apps/terminal/migrations/0060_sessionsharing_action_permission.py b/apps/terminal/migrations/0060_sessionsharing_action_permission.py new file mode 100644 index 000000000..223526bb0 --- /dev/null +++ b/apps/terminal/migrations/0060_sessionsharing_action_permission.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.17 on 2023-04-03 04:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0059_session_account_id'), + ] + + operations = [ + migrations.AddField( + model_name='sessionsharing', + name='action_permission', + field=models.IntegerField(default=1, verbose_name='Action permission'), + ), + ] diff --git a/apps/terminal/models/session/sharing.py b/apps/terminal/models/session/sharing.py index b71cad900..1abeb7959 100644 --- a/apps/terminal/models/session/sharing.py +++ b/apps/terminal/models/session/sharing.py @@ -31,6 +31,10 @@ class SessionSharing(JMSBaseModel, OrgModelMixin): ) users = models.TextField(blank=True, verbose_name=_("User")) + action_permission = models.IntegerField( + default=1, verbose_name=_('Action permission') + ) + class Meta: ordering = ('-date_created',) verbose_name = _('Session sharing') @@ -142,3 +146,6 @@ class SessionJoinRecord(JMSBaseModel, OrgModelMixin): self.date_left = timezone.now() self.is_finished = True self.save() + @property + def action_permission(self): + return self.sharing.action_permission diff --git a/apps/terminal/serializers/sharing.py b/apps/terminal/serializers/sharing.py index faf4515ad..5427fc7eb 100644 --- a/apps/terminal/serializers/sharing.py +++ b/apps/terminal/serializers/sharing.py @@ -1,8 +1,11 @@ -from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from common.utils.random import random_string +from rest_framework import serializers + +from common.serializers.fields import LabeledChoiceField from common.utils.common import pretty_string +from common.utils.random import random_string +from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from ..const import ActionPermission from ..models import SessionSharing, SessionJoinRecord __all__ = ['SessionSharingSerializer', 'SessionJoinRecordSerializer'] @@ -12,13 +15,17 @@ class SessionSharingSerializer(OrgResourceModelSerializerMixin): users = serializers.ListSerializer( child=serializers.CharField(max_length=36), allow_null=True, write_only=True ) + action_permission = LabeledChoiceField( + default=1, choices=ActionPermission.choices, write_only=True, label=_('Action permission') + ) class Meta: model = SessionSharing fields_mini = ['id'] fields_small = fields_mini + [ 'verify_code', 'is_active', 'expired_time', 'created_by', - 'date_created', 'date_updated', 'users', 'users_display' + 'date_created', 'date_updated', 'users', 'users_display', + 'action_permission' ] fields_fk = ['session', 'creator'] fields = fields_small + fields_fk @@ -40,13 +47,17 @@ class SessionSharingSerializer(OrgResourceModelSerializerMixin): class SessionJoinRecordSerializer(OrgResourceModelSerializerMixin): + action_permission = LabeledChoiceField( + choices=ActionPermission.choices, read_only=True, label=_('Action permission') + ) + class Meta: model = SessionJoinRecord fields_mini = ['id'] fields_small = fields_mini + [ 'joiner_display', 'verify_code', 'date_joined', 'date_left', 'remote_addr', 'login_from', 'is_success', 'reason', 'is_finished', - 'created_by', 'date_created', 'date_updated' + 'created_by', 'date_created', 'date_updated', 'action_permission' ] fields_fk = ['session', 'sharing', 'joiner'] fields = fields_small + fields_fk From 1fbaa8517841c5bfe5797b134de1e4b48fcc4acc Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 3 Apr 2023 15:01:48 +0800 Subject: [PATCH 061/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/const.py | 8 ++++---- .../migrations/0060_sessionsharing_action_permission.py | 4 ++-- apps/terminal/models/session/sharing.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 20b82d775..206fcef74 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from django.db.models import TextChoices, IntegerChoices +from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ @@ -67,6 +67,6 @@ class SessionType(TextChoices): command = 'command', _('Command') -class ActionPermission(IntegerChoices): - read_only = 0, _('Read Only') - writable = 1, _('Writable') +class ActionPermission(TextChoices): + read_only = "readonly", _('Read Only') + writable = "writable", _('Writable') diff --git a/apps/terminal/migrations/0060_sessionsharing_action_permission.py b/apps/terminal/migrations/0060_sessionsharing_action_permission.py index 223526bb0..1e0bcab94 100644 --- a/apps/terminal/migrations/0060_sessionsharing_action_permission.py +++ b/apps/terminal/migrations/0060_sessionsharing_action_permission.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.17 on 2023-04-03 04:58 +# Generated by Django 3.2.17 on 2023-04-03 06:28 from django.db import migrations, models @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='sessionsharing', name='action_permission', - field=models.IntegerField(default=1, verbose_name='Action permission'), + field=models.CharField(default='writable', max_length=16, verbose_name='Action permission'), ), ] diff --git a/apps/terminal/models/session/sharing.py b/apps/terminal/models/session/sharing.py index 1abeb7959..061ffad4a 100644 --- a/apps/terminal/models/session/sharing.py +++ b/apps/terminal/models/session/sharing.py @@ -31,8 +31,8 @@ class SessionSharing(JMSBaseModel, OrgModelMixin): ) users = models.TextField(blank=True, verbose_name=_("User")) - action_permission = models.IntegerField( - default=1, verbose_name=_('Action permission') + action_permission = models.CharField( + max_length=16, verbose_name=_('Action permission'), default='writable' ) class Meta: From d9ad5aee4a33765f65e29ffbbbf22a122d3b539a Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 3 Apr 2023 15:37:32 +0800 Subject: [PATCH 062/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E5=92=8C=E5=8F=98=E9=87=8F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/const.py | 2 +- apps/terminal/serializers/sharing.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 206fcef74..fd0421427 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -68,5 +68,5 @@ class SessionType(TextChoices): class ActionPermission(TextChoices): - read_only = "readonly", _('Read Only') + readonly = "readonly", _('Read Only') writable = "writable", _('Writable') diff --git a/apps/terminal/serializers/sharing.py b/apps/terminal/serializers/sharing.py index 5427fc7eb..c7b8f2e4e 100644 --- a/apps/terminal/serializers/sharing.py +++ b/apps/terminal/serializers/sharing.py @@ -16,7 +16,8 @@ class SessionSharingSerializer(OrgResourceModelSerializerMixin): child=serializers.CharField(max_length=36), allow_null=True, write_only=True ) action_permission = LabeledChoiceField( - default=1, choices=ActionPermission.choices, write_only=True, label=_('Action permission') + default=ActionPermission.writable, choices=ActionPermission.choices, + write_only=True, label=_('Action permission') ) class Meta: From 90cbf653ac16ed7b1d3a6942a1fbbb1590c1f78b Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:47:14 +0800 Subject: [PATCH 063/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96luna=20tree?= =?UTF-8?q?=20title=20(#10118)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 6cc198169..245ac22dd 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -52,7 +52,7 @@ class SerializeToTreeNodeMixin: { 'id': str(asset.id), 'name': asset.name, - 'title': asset.address, + 'title': f'{asset.address}\n{asset.comment}', 'pId': get_pid(asset), 'isParent': False, 'open': False, From 34c9044d031601c630883ccd570acc1eca080913 Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 3 Apr 2023 16:45:59 +0800 Subject: [PATCH 064/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20LDAP=20?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E7=94=A8=E6=88=B7=E6=97=B6=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E7=BB=84=E7=BB=87=EF=BC=8C=E8=BF=98=E4=BC=9A?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=88=B0=20Default=20=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/tasks/ldap.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/settings/tasks/ldap.py b/apps/settings/tasks/ldap.py index 226a8139c..8a4658f2b 100644 --- a/apps/settings/tasks/ldap.py +++ b/apps/settings/tasks/ldap.py @@ -1,8 +1,9 @@ # coding: utf-8 -# +# from celery import shared_task from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from django.db import transaction from common.utils import get_logger from ops.celery.decorator import after_app_ready_start @@ -22,6 +23,7 @@ def sync_ldap_user(): @shared_task(verbose_name=_('Import ldap user')) +@transaction.atomic def import_ldap_user(): logger.info("Start import ldap user task") util_server = LDAPServerUtil() From 4601bb9e587d5130e3b4a483b88581c476560851 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 3 Apr 2023 17:50:52 +0800 Subject: [PATCH 065/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96mac=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=90=8D=E5=AD=97=20(#10122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/templates/resource_download.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index d207335d8..a477df5ba 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -21,8 +21,8 @@ p {

From c5340b5adcf77583c6e1563769e6e06c9000c96c Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:18:31 +0800 Subject: [PATCH 066/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20account=20?= =?UTF-8?q?(#10088)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化账号创建策略 * perf: 修改账号 * perf: 修改 account * perf: 修改 account * perf: 修改批量创建 * perf: 修改账号批量创建 * perf: 继续优化账号批量添加 * perf: 优化创建 accounts 的结果 * perf: 优化账号批量返回的格式 * perf: 优化账号 --------- Co-authored-by: ibuler --- apps/accounts/api/account/account.py | 18 +- apps/accounts/const/account.py | 2 +- .../migrations/0010_account_source_id.py | 18 + apps/accounts/models/account.py | 1 + apps/accounts/serializers/account/account.py | 378 ++++++++++++++---- apps/accounts/serializers/account/base.py | 3 +- apps/accounts/signal_handlers.py | 4 +- apps/accounts/urls.py | 1 + apps/accounts/validator.py | 101 ----- apps/assets/const/protocol.py | 8 + .../0112_platformprotocol_public.py | 18 + apps/assets/models/platform.py | 1 + apps/assets/serializers/asset/common.py | 56 +-- apps/terminal/models/applet/applet.py | 6 +- 14 files changed, 378 insertions(+), 237 deletions(-) create mode 100644 apps/accounts/migrations/0010_account_source_id.py delete mode 100644 apps/accounts/validator.py create mode 100644 apps/assets/migrations/0112_platformprotocol_public.py diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py index 0cfddd88c..7b9991988 100644 --- a/apps/accounts/api/account/account.py +++ b/apps/accounts/api/account/account.py @@ -1,6 +1,6 @@ from django.shortcuts import get_object_or_404 from rest_framework.decorators import action -from rest_framework.generics import ListAPIView +from rest_framework.generics import ListAPIView, CreateAPIView from rest_framework.response import Response from rest_framework.status import HTTP_200_OK @@ -15,7 +15,7 @@ from rbac.permissions import RBACPermission __all__ = [ 'AccountViewSet', 'AccountSecretsViewSet', - 'AccountHistoriesSecretAPI' + 'AccountHistoriesSecretAPI', 'AssetAccountBulkCreateApi', ] @@ -97,6 +97,20 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): } +class AssetAccountBulkCreateApi(CreateAPIView): + serializer_class = serializers.AssetAccountBulkSerializer + rbac_perms = { + 'POST': 'accounts.add_account', + } + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + data = serializer.create(serializer.validated_data) + serializer = serializers.AssetAccountBulkSerializerResultSerializer(data, many=True) + return Response(data=serializer.data, status=HTTP_200_OK) + + class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView): model = Account.history.model serializer_class = serializers.AccountHistorySerializer diff --git a/apps/accounts/const/account.py b/apps/accounts/const/account.py index b86e9400b..29185b233 100644 --- a/apps/accounts/const/account.py +++ b/apps/accounts/const/account.py @@ -20,7 +20,7 @@ class Source(TextChoices): COLLECTED = 'collected', _('Collected') -class BulkCreateStrategy(TextChoices): +class AccountInvalidPolicy(TextChoices): SKIP = 'skip', _('Skip') UPDATE = 'update', _('Update') ERROR = 'error', _('Failed') diff --git a/apps/accounts/migrations/0010_account_source_id.py b/apps/accounts/migrations/0010_account_source_id.py new file mode 100644 index 000000000..dc1a5563c --- /dev/null +++ b/apps/accounts/migrations/0010_account_source_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.17 on 2023-03-23 07:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0009_account_usernames_to_ids'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='source_id', + field=models.CharField(max_length=128, null=True, blank=True, verbose_name='Source ID'), + ), + ] diff --git a/apps/accounts/models/account.py b/apps/accounts/models/account.py index 008318c7e..4094018e1 100644 --- a/apps/accounts/models/account.py +++ b/apps/accounts/models/account.py @@ -53,6 +53,7 @@ class Account(AbsConnectivity, BaseAccount): version = models.IntegerField(default=0, verbose_name=_('Version')) history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version']) source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source')) + source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID')) class Meta: verbose_name = _('Account') diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index b8a5d84c0..97bbbfaa7 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -1,15 +1,18 @@ +import uuid +from collections import defaultdict + +from django.db import IntegrityError +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from rest_framework.generics import get_object_or_404 from rest_framework.validators import UniqueTogetherValidator -from accounts import validator -from accounts.const import SecretType, Source, BulkCreateStrategy +from accounts.const import SecretType, Source, AccountInvalidPolicy from accounts.models import Account, AccountTemplate from accounts.tasks import push_accounts_to_assets_task -from assets.const import Category, AllTypes +from assets.const import Category, AllTypes, Protocol from assets.models import Asset -from common.serializers import SecretReadableMixin, BulkModelSerializer +from common.serializers import SecretReadableMixin from common.serializers.fields import ObjectRelatedField, LabeledChoiceField from common.utils import get_logger from .base import BaseAccountSerializer @@ -17,74 +20,134 @@ from .base import BaseAccountSerializer logger = get_logger(__name__) -class AccountSerializerCreateValidateMixin: - from_id: str - template: bool - push_now: bool - replace_attrs: callable +class AccountCreateUpdateSerializerMixin(serializers.Serializer): + template = serializers.PrimaryKeyRelatedField( + queryset=AccountTemplate.objects, + required=False, label=_("Template"), write_only=True + ) + push_now = serializers.BooleanField( + default=False, label=_("Push now"), write_only=True + ) + on_invalid = LabeledChoiceField( + choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR, + write_only=True, label=_('Exist policy') + ) + + class Meta: + fields = ['template', 'push_now', 'on_invalid'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_initial_value() + + def set_initial_value(self): + if not getattr(self, 'initial_data', None): + return + if isinstance(self.initial_data, dict): + initial_data = [self.initial_data] + else: + initial_data = self.initial_data + + for data in initial_data: + if not data.get('asset') and not self.instance: + raise serializers.ValidationError({'asset': 'Asset is required'}) + asset = data.get('asset') or self.instance.asset + self.from_template_if_need(data) + self.set_uniq_name_if_need(data, asset) - def to_internal_value(self, data): - from_id = data.pop('id', None) - ret = super().to_internal_value(data) - self.from_id = from_id - return ret @staticmethod - def related_template_values(template: AccountTemplate, attrs): - ignore_fields = ['id', 'date_created', 'date_updated', 'org_id'] + def set_uniq_name_if_need(initial_data, asset): + name = initial_data.get('name') + if not name: + name = initial_data.get('username') + if Account.objects.filter(name=name, asset=asset).exists(): + name = name + '_' + uuid.uuid4().hex[:4] + initial_data['name'] = name + + @staticmethod + def from_template_if_need(initial_data): + template_id = initial_data.pop('template', None) + if not template_id: + return + if isinstance(template_id, (str, uuid.UUID)): + template = AccountTemplate.objects.filter(id=template_id).first() + else: + template = template_id + if not template: + raise serializers.ValidationError({'template': 'Template not found'}) + + # Set initial data from template + ignore_fields = ['id', 'name', 'date_created', 'date_updated', 'org_id'] field_names = [ field.name for field in template._meta.fields if field.name not in ignore_fields ] + attrs = {'source': 'template', 'source_id': template.id} for name in field_names: - attrs[name] = attrs.get(name) or getattr(template, name) - - def set_secret(self, attrs): - _id = self.from_id - template = attrs.pop('template', None) - - if _id and template: - account_template = get_object_or_404(AccountTemplate, id=_id) - self.related_template_values(account_template, attrs) - elif _id and not template: - account = get_object_or_404(Account, id=_id) - attrs['secret'] = account.secret - return attrs - - def validate(self, attrs): - attrs = super().validate(attrs) - return self.set_secret(attrs) + value = getattr(template, name, None) + if value is None: + continue + attrs[name] = value + initial_data.update(attrs) @staticmethod - def push_account(instance, push_now): - if not push_now: + def push_account_if_need(instance, push_now, stat): + if not push_now or stat != 'created': return push_accounts_to_assets_task.delay([str(instance.id)]) + def get_validators(self): + _validators = super().get_validators() + if getattr(self, 'initial_data', None) is None: + return _validators + on_invalid = self.initial_data.get('on_invalid') + if on_invalid == AccountInvalidPolicy.ERROR: + return _validators + _validators = [v for v in _validators if not isinstance(v, UniqueTogetherValidator)] + return _validators + + @staticmethod + def do_create(vd): + on_invalid = vd.pop('on_invalid', None) + + q = Q() + if vd.get('name'): + q |= Q(name=vd['name']) + if vd.get('username'): + q |= Q(username=vd['username'], secret_type=vd.get('secret_type')) + + instance = Account.objects.filter(asset=vd['asset']).filter(q).first() + # 不存在这个资产,不用关系策略 + if not instance: + instance = Account.objects.create(**vd) + return instance, 'created' + + if on_invalid == AccountInvalidPolicy.SKIP: + return instance, 'skipped' + elif on_invalid == AccountInvalidPolicy.UPDATE: + for k, v in vd.items(): + setattr(instance, k, v) + instance.save() + return instance, 'updated' + else: + raise serializers.ValidationError('Account already exists') + def create(self, validated_data): push_now = validated_data.pop('push_now', None) - instance = super().create(validated_data) - self.push_account(instance, push_now) + instance, stat = self.do_create(validated_data) + self.push_account_if_need(instance, push_now, stat) return instance def update(self, instance, validated_data): # account cannot be modified validated_data.pop('username', None) + validated_data.pop('on_invalid', None) push_now = validated_data.pop('push_now', None) instance = super().update(instance, validated_data) - self.push_account(instance, push_now) + self.push_account_if_need(instance, push_now, 'updated') return instance -class AccountSerializerCreateMixin(AccountSerializerCreateValidateMixin, BulkModelSerializer): - template = serializers.BooleanField( - default=False, label=_("Template"), write_only=True - ) - push_now = serializers.BooleanField( - default=False, label=_("Push now"), write_only=True - ) - has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True) - - class AccountAssetSerializer(serializers.ModelSerializer): platform = ObjectRelatedField(read_only=True) category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) @@ -106,62 +169,207 @@ class AccountAssetSerializer(serializers.ModelSerializer): raise serializers.ValidationError(_('Asset not found')) -class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): +class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer): asset = AccountAssetSerializer(label=_('Asset')) source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True) + has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True) su_from = ObjectRelatedField( required=False, queryset=Account.objects, allow_null=True, allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username') ) - strategy = LabeledChoiceField( - choices=BulkCreateStrategy.choices, default=BulkCreateStrategy.SKIP, - write_only=True, label=_('Account policy') - ) class Meta(BaseAccountSerializer.Meta): model = Account fields = BaseAccountSerializer.Meta.fields + [ - 'su_from', 'asset', 'template', 'version', - 'push_now', 'source', 'connectivity', 'strategy' + 'su_from', 'asset', 'version', + 'source', 'source_id', 'connectivity', + ] + AccountCreateUpdateSerializerMixin.Meta.fields + read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [ + 'source', 'source_id', 'connectivity' ] extra_kwargs = { **BaseAccountSerializer.Meta.extra_kwargs, - 'name': {'required': False, 'allow_null': True}, + 'name': {'required': False}, } - def validate_name(self, value): - if not value: - value = self.initial_data.get('username') - return value - @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset \ - .prefetch_related('asset', 'asset__platform', 'asset__platform__automation') + queryset = queryset.prefetch_related( + 'asset', 'asset__platform', + 'asset__platform__automation' + ) return queryset - def get_validators(self): - ignore = False - validators = [validator.AccountSecretTypeValidator(fields=('secret_type',))] - view = self.context.get('view') - request = self.context.get('request') - if request and view: - data = request.data - action = view.action - ignore = action == 'create' and isinstance(data, list) - _validators = super().get_validators() - for v in _validators: - if ignore and isinstance(v, UniqueTogetherValidator): - v = validator.AccountUniqueTogetherValidator(v.queryset, v.fields) - validators.append(v) - return validators +class AssetAccountBulkSerializerResultSerializer(serializers.Serializer): + asset = serializers.CharField(read_only=True, label=_('Asset')) + state = serializers.CharField(read_only=True, label=_('State')) + error = serializers.CharField(read_only=True, label=_('Error')) + changed = serializers.BooleanField(read_only=True, label=_('Changed')) - def validate(self, attrs): - attrs = super().validate(attrs) - attrs.pop('strategy', None) - return attrs + +class AssetAccountBulkSerializer(AccountCreateUpdateSerializerMixin, serializers.ModelSerializer): + assets = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, many=True, label=_('Assets')) + + class Meta: + model = Account + fields = [ + 'name', 'username', 'secret', 'secret_type', + 'privileged', 'is_active', 'comment', 'template', + 'on_invalid', 'push_now', 'assets', + ] + extra_kwargs = { + 'name': {'required': False}, + 'secret_type': {'required': False}, + } + + def set_initial_value(self): + if not getattr(self, 'initial_data', None): + return + initial_data = self.initial_data + self.from_template_if_need(initial_data) + + @staticmethod + def _get_valid_secret_type_assets(assets, secret_type): + if isinstance(assets, list): + asset_ids = [a.id for a in assets] + assets = Asset.objects.filter(id__in=asset_ids) + + asset_protocol = assets.prefetch_related('protocols').values_list('id', 'protocols__name') + protocol_secret_types_map = Protocol.protocol_secret_types() + asset_secret_types_mapp = defaultdict(set) + + for asset_id, protocol in asset_protocol: + secret_types = set(protocol_secret_types_map.get(protocol, [])) + asset_secret_types_mapp[asset_id].update(secret_types) + + return [ + asset for asset in assets + if secret_type in asset_secret_types_mapp.get(asset.id, []) + ] + + @staticmethod + def get_filter_lookup(vd): + return { + 'username': vd['username'], + 'secret_type': vd['secret_type'], + 'asset': vd['asset'], + } + + @staticmethod + def get_uniq_name(vd): + return vd['name'] + '-' + uuid.uuid4().hex[:4] + + @staticmethod + def _handle_update_create(vd, lookup): + ori = Account.objects.filter(**lookup).first() + if ori and ori.secret == vd['secret']: + return ori, False, 'skipped' + + instance, value = Account.objects.update_or_create(defaults=vd, **lookup) + state = 'created' if value else 'updated' + return instance, True, state + + @staticmethod + def _handle_skip_create(vd, lookup): + instance, value = Account.objects.get_or_create(defaults=vd, **lookup) + state = 'created' if value else 'skipped' + return instance, value, state + + @staticmethod + def _handle_err_create(vd, lookup): + instance, value = Account.objects.get_or_create(defaults=vd, **lookup) + if not value: + raise serializers.ValidationError(_('Account already exists')) + return instance, True, 'created' + + def perform_create(self, vd, handler): + lookup = self.get_filter_lookup(vd) + try: + instance, changed, state = handler(vd, lookup) + except IntegrityError: + vd['name'] = self.get_uniq_name(vd) + instance, changed, state = handler(vd, lookup) + return instance, changed, state + + def get_create_handler(self, on_invalid): + if on_invalid == 'update': + handler = self._handle_update_create + elif on_invalid == 'skip': + handler = self._handle_skip_create + else: + handler = self._handle_err_create + return handler + + def perform_bulk_create(self, vd): + assets = vd.pop('assets') + on_invalid = vd.pop('on_invalid', 'skip') + secret_type = vd.get('secret_type', 'password') + + if not vd.get('name'): + vd['name'] = vd.get('username') + + create_handler = self.get_create_handler(on_invalid) + secret_type_supports = self._get_valid_secret_type_assets(assets, secret_type) + + _results = {} + for asset in assets: + if asset not in secret_type_supports: + _results[asset] = { + 'error': _('Asset does not support this secret type: %s') % secret_type, + 'state': 'error', + } + continue + + vd = vd.copy() + vd['asset'] = asset + try: + instance, changed, state = self.perform_create(vd, create_handler) + _results[asset] = { + 'changed': changed, 'instance': instance.id, 'state': state + } + except serializers.ValidationError as e: + _results[asset] = {'error': e.detail[0], 'state': 'error'} + except Exception as e: + logger.exception(e) + _results[asset] = {'error': str(e), 'state': 'error'} + + results = [{'asset': asset, **result} for asset, result in _results.items()] + state_score = {'created': 3, 'updated': 2, 'skipped': 1, 'error': 0} + results = sorted(results, key=lambda x: state_score.get(x['state'], 4)) + + if on_invalid != 'error': + return results + + errors = [] + errors.extend([result for result in results if result['state'] == 'error']) + for result in results: + if result['state'] != 'skipped': + continue + errors.append({ + 'error': _('Account has exist'), + 'state': 'error', + 'asset': str(result['asset']) + }) + if errors: + raise serializers.ValidationError(errors) + return results + + @staticmethod + def push_accounts_if_need(results, push_now): + if not push_now: + return + accounts = [str(v['instance']) for v in results if v.get('instance')] + push_accounts_to_assets_task.delay(accounts) + + def create(self, validated_data): + push_now = validated_data.pop('push_now', False) + results = self.perform_bulk_create(validated_data) + self.push_accounts_if_need(results, push_now) + for res in results: + res['asset'] = str(res['asset']) + return results class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): @@ -177,8 +385,8 @@ class AccountHistorySerializer(serializers.ModelSerializer): class Meta: model = Account.history.model fields = [ - 'id', 'secret', 'secret_type', 'version', 'history_date', - 'history_user' + 'id', 'secret', 'secret_type', 'version', + 'history_date', 'history_user' ] read_only_fields = fields extra_kwargs = { diff --git a/apps/accounts/serializers/account/base.py b/apps/accounts/serializers/account/base.py index 88239c98e..4e2b1a1df 100644 --- a/apps/accounts/serializers/account/base.py +++ b/apps/accounts/serializers/account/base.py @@ -13,7 +13,7 @@ __all__ = ['AuthValidateMixin', 'BaseAccountSerializer'] class AuthValidateMixin(serializers.Serializer): secret_type = LabeledChoiceField( - choices=SecretType.choices, required=True, label=_('Secret type') + choices=SecretType.choices, label=_('Secret type'), default='password' ) secret = EncryptedField( label=_('Secret'), required=False, max_length=40960, allow_blank=True, @@ -77,6 +77,5 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): 'date_verified', 'created_by', 'date_created', ] extra_kwargs = { - 'name': {'required': True}, 'spec_info': {'label': _('Spec info')}, } diff --git a/apps/accounts/signal_handlers.py b/apps/accounts/signal_handlers.py index b47588192..cf09842cc 100644 --- a/apps/accounts/signal_handlers.py +++ b/apps/accounts/signal_handlers.py @@ -8,8 +8,8 @@ logger = get_logger(__name__) @receiver(pre_save, sender=Account) -def on_account_pre_save(sender, instance, created=False, **kwargs): - if created: +def on_account_pre_save(sender, instance, **kwargs): + if instance.version == 0: instance.version = 1 else: instance.version = instance.history.count() diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py index 59a70b3cf..5c57ad67b 100644 --- a/apps/accounts/urls.py +++ b/apps/accounts/urls.py @@ -25,6 +25,7 @@ router.register(r'push-account-executions', api.PushAccountExecutionViewSet, 'pu router.register(r'push-account-records', api.PushAccountRecordViewSet, 'push-account-record') urlpatterns = [ + path('accounts/bulk/', api.AssetAccountBulkCreateApi.as_view(), name='account-bulk-create'), path('accounts/tasks/', api.AccountsTaskCreateAPI.as_view(), name='account-task-create'), path('account-secrets//histories/', api.AccountHistoriesSecretAPI.as_view(), name='account-secret-history'), diff --git a/apps/accounts/validator.py b/apps/accounts/validator.py deleted file mode 100644 index b8c49896d..000000000 --- a/apps/accounts/validator.py +++ /dev/null @@ -1,101 +0,0 @@ -from functools import reduce - -from django.utils.translation import ugettext_lazy as _ -from rest_framework.validators import ( - UniqueTogetherValidator, ValidationError -) - -from accounts.const import BulkCreateStrategy -from accounts.models import Account -from assets.const import Protocol - -__all__ = ['AccountUniqueTogetherValidator', 'AccountSecretTypeValidator'] - - -class ValidatorStrategyMixin: - - @staticmethod - def get_strategy(attrs): - return attrs.get('strategy', BulkCreateStrategy.SKIP) - - def __call__(self, attrs, serializer): - message = None - try: - super().__call__(attrs, serializer) - except ValidationError as e: - message = e.detail[0] - strategy = self.get_strategy(attrs) - if not message: - return - if strategy == BulkCreateStrategy.ERROR: - raise ValidationError(message, code='error') - elif strategy in [BulkCreateStrategy.SKIP, BulkCreateStrategy.UPDATE]: - raise ValidationError({}) - else: - return - - -class SecretTypeValidator: - requires_context = True - protocol_settings = Protocol.settings() - message = _('{field_name} not a legal option') - - def __init__(self, fields): - self.fields = fields - - def __call__(self, attrs, serializer): - secret_types = set() - if serializer.instance: - asset = serializer.instance.asset - else: - asset = attrs['asset'] - secret_type = attrs['secret_type'] - platform_protocols_dict = { - name: self.protocol_settings.get(name, {}).get('secret_types', []) - for name in asset.platform.protocols.values_list('name', flat=True) - } - - for name in asset.protocols.values_list('name', flat=True): - if name in platform_protocols_dict: - secret_types |= set(platform_protocols_dict[name]) - if secret_type not in secret_types: - message = self.message.format(field_name=secret_type) - raise ValidationError(message, code='error') - - -class UpdateAccountMixin: - fields: tuple - get_strategy: callable - - def update(self, attrs): - unique_together = Account._meta.unique_together - unique_together_fields = reduce(lambda x, y: set(x) | set(y), unique_together) - query = {field_name: attrs[field_name] for field_name in unique_together_fields} - account = Account.objects.filter(**query).first() - if not account: - query = {field_name: attrs[field_name] for field_name in self.fields} - account = Account.objects.filter(**query).first() - - for k, v in attrs.items(): - setattr(account, k, v) - account.save() - - def __call__(self, attrs, serializer): - try: - super().__call__(attrs, serializer) - except ValidationError as e: - strategy = self.get_strategy(attrs) - if strategy == BulkCreateStrategy.UPDATE: - self.update(attrs) - message = e.detail[0] - raise ValidationError(message, code='unique') - - -class AccountUniqueTogetherValidator( - ValidatorStrategyMixin, UpdateAccountMixin, UniqueTogetherValidator -): - pass - - -class AccountSecretTypeValidator(ValidatorStrategyMixin, SecretTypeValidator): - pass diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index 6523c5dcc..39d3f3112 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -128,3 +128,11 @@ class Protocol(ChoicesMixin, models.TextChoices): **cls.database_protocols(), **cls.cloud_protocols() } + + @classmethod + def protocol_secret_types(cls): + settings = cls.settings() + return { + protocol: settings[protocol]['secret_types'] + for protocol in cls.settings() + } diff --git a/apps/assets/migrations/0112_platformprotocol_public.py b/apps/assets/migrations/0112_platformprotocol_public.py new file mode 100644 index 000000000..917686bc6 --- /dev/null +++ b/apps/assets/migrations/0112_platformprotocol_public.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.17 on 2023-03-24 03:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0111_auto_20230321_1633'), + ] + + operations = [ + migrations.AddField( + model_name='platformprotocol', + name='public', + field=models.BooleanField(default=True, verbose_name='Public'), + ), + ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index e2262af04..7f57c01d7 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -15,6 +15,7 @@ class PlatformProtocol(models.Model): primary = models.BooleanField(default=False, verbose_name=_('Primary')) required = models.BooleanField(default=False, verbose_name=_('Required')) default = models.BooleanField(default=False, verbose_name=_('Default')) + public = models.BooleanField(default=True, verbose_name=_('Public')) setting = models.JSONField(verbose_name=_('Setting'), default=dict) platform = models.ForeignKey('Platform', on_delete=models.CASCADE, related_name='protocols') diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 0a544a7b2..22329b22a 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -6,9 +6,8 @@ from django.db.transaction import atomic from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from accounts.const import SecretType from accounts.models import Account -from accounts.serializers import AuthValidateMixin, AccountSerializerCreateValidateMixin +from accounts.serializers import AccountSerializer from common.serializers import WritableNestedModelSerializer, SecretReadableMixin, CommonModelSerializer from common.serializers.fields import LabeledChoiceField from orgs.mixins.serializers import BulkOrgResourceModelSerializer @@ -59,49 +58,19 @@ class AssetPlatformSerializer(serializers.ModelSerializer): } -class AssetAccountSerializer( - AuthValidateMixin, - AccountSerializerCreateValidateMixin, - CommonModelSerializer -): +class AssetAccountSerializer(AccountSerializer): add_org_fields = False - push_now = serializers.BooleanField( - default=False, label=_("Push now"), write_only=True - ) - template = serializers.BooleanField( - default=False, label=_("Template"), write_only=True - ) - name = serializers.CharField(max_length=128, required=False, label=_("Name")) - secret_type = LabeledChoiceField( - choices=SecretType.choices, default=SecretType.PASSWORD, - required=False, label=_('Secret type') - ) + asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, required=False, write_only=True) - class Meta: - model = Account - fields_mini = [ - 'id', 'name', 'username', 'privileged', - 'is_active', 'version', 'secret_type', + class Meta(AccountSerializer.Meta): + fields = [ + f for f in AccountSerializer.Meta.fields + if f not in ['spec_info'] ] - fields_write_only = [ - 'secret', 'passphrase', 'push_now', 'template' - ] - fields = fields_mini + fields_write_only extra_kwargs = { - 'secret': {'write_only': True}, + **AccountSerializer.Meta.extra_kwargs, } - def validate_push_now(self, value): - request = self.context['request'] - if not request.user.has_perms('accounts.push_account'): - return False - return value - - def validate_name(self, value): - if not value: - value = self.initial_data.get('username') - return value - class AccountSecretSerializer(SecretReadableMixin, CommonModelSerializer): class Meta: @@ -132,7 +101,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type')) labels = AssetLabelSerializer(many=True, required=False, label=_('Label')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=()) - accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Account')) + accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, label=_('Account')) nodes_display = serializers.ListField(read_only=False, required=False, label=_("Node path")) class Meta: @@ -280,8 +249,11 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali if not accounts_data: return for data in accounts_data: - data['asset'] = asset - AssetAccountSerializer().create(data) + data['asset'] = asset.id + + s = AssetAccountSerializer(data=accounts_data, many=True) + s.is_valid(raise_exception=True) + s.save() @atomic def create(self, validated_data): diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 22afe39e8..bb6e11179 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -112,8 +112,10 @@ class Applet(JMSBaseModel): def select_host_account(self): # 选择激活的发布机 - hosts = [item for item in self.hosts.filter(is_active=True).all() - if item.load != 'offline'] + hosts = [ + host for host in self.hosts.filter(is_active=True) + if host.load != 'offline' + ] if not hosts: return None From 9686c66874eeb5b5f0086c21ec7e7b8d8dde4539 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 3 Apr 2023 18:10:48 +0800 Subject: [PATCH 067/100] =?UTF-8?q?perf:=20=E4=BC=9A=E8=AF=9D=E5=88=86?= =?UTF-8?q?=E4=BA=AB=E8=AE=B0=E5=BD=95=E5=AD=97=E6=AE=B5=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.po | 141 ++++++++++++++---------- apps/locale/zh/LC_MESSAGES/django.po | 137 +++++++++++++---------- apps/terminal/models/session/sharing.py | 1 + 3 files changed, 165 insertions(+), 114 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index e9145956c..e5af1f351 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-03-27 17:00+0800\n" +"POT-Creation-Date: 2023-04-03 18:06+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -292,7 +292,7 @@ msgid "Trigger mode" msgstr "トリガーモード" #: accounts/models/automations/backup_account.py:97 audits/models.py:172 -#: terminal/models/session/sharing.py:107 xpack/plugins/cloud/models.py:168 +#: terminal/models/session/sharing.py:111 xpack/plugins/cloud/models.py:168 msgid "Reason" msgstr "理由" @@ -432,7 +432,13 @@ msgstr "最終ログインアドレス" msgid "Gather account automation" msgstr "自動収集アカウント" -#: accounts/models/automations/gather_account.py:39 +#: accounts/models/automations/gather_account.py:56 +#, fuzzy +#| msgid "Is service account" +msgid "Is sync account" +msgstr "サービスアカウントです" + +#: accounts/models/automations/gather_account.py:71 #: accounts/tasks/gather_accounts.py:29 msgid "Gather asset accounts" msgstr "アカウントのコレクション" @@ -465,7 +471,7 @@ msgstr "アカウントの確認" #: assets/models/group.py:20 assets/models/label.py:18 #: assets/models/platform.py:13 assets/models/platform.py:64 #: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:155 -#: assets/serializers/platform.py:141 +#: assets/serializers/platform.py:148 #: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 @@ -474,7 +480,7 @@ msgstr "アカウントの確認" #: terminal/models/applet/applet.py:27 terminal/models/component/endpoint.py:12 #: terminal/models/component/endpoint.py:90 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:15 -#: terminal/models/component/terminal.py:79 users/forms/profile.py:33 +#: terminal/models/component/terminal.py:84 users/forms/profile.py:33 #: users/models/group.py:13 users/models/user.py:717 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -551,7 +557,7 @@ msgstr "エスクローされたパスワード" #: accounts/serializers/account/account.py:90 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:65 #: assets/serializers/asset/common.py:131 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:92 assets/serializers/platform.py:142 +#: assets/serializers/platform.py:100 assets/serializers/platform.py:149 #: perms/serializers/user_permission.py:26 settings/models.py:35 #: tickets/models/ticket/apply_application.py:13 msgid "Category" @@ -562,7 +568,7 @@ msgstr "カテゴリ" #: 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:66 -#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:91 +#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:99 #: audits/serializers.py:48 #: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 @@ -683,7 +689,7 @@ msgstr "自動タスク実行履歴" #: accounts/serializers/automations/change_secret.py:154 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:103 +#: terminal/const.py:59 terminal/models/session/sharing.py:107 #: tickets/views/approve.py:114 msgid "Success" msgstr "成功" @@ -1309,7 +1315,7 @@ msgstr "アセットの自動化タスク" #: assets/models/automations/base.py:112 audits/models.py:177 #: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:183 #: terminal/models/applet/applet.py:157 terminal/models/applet/host.py:108 -#: terminal/models/component/status.py:27 terminal/serializers/applet.py:18 +#: 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 #: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:164 @@ -1504,23 +1510,23 @@ msgstr "メタ" msgid "Internal" msgstr "ビルトイン" -#: assets/models/platform.py:71 assets/serializers/platform.py:89 +#: assets/models/platform.py:71 assets/serializers/platform.py:97 msgid "Charset" msgstr "シャーセット" -#: assets/models/platform.py:73 assets/serializers/platform.py:117 +#: assets/models/platform.py:73 assets/serializers/platform.py:124 msgid "Domain enabled" msgstr "ドメインを有効にする" -#: assets/models/platform.py:75 assets/serializers/platform.py:116 +#: assets/models/platform.py:75 assets/serializers/platform.py:123 msgid "Su enabled" msgstr "アカウントの切り替えを有効にする" -#: assets/models/platform.py:76 assets/serializers/platform.py:99 +#: assets/models/platform.py:76 assets/serializers/platform.py:106 msgid "Su method" msgstr "アカウントの切り替え方法" -#: assets/models/platform.py:78 assets/serializers/platform.py:96 +#: assets/models/platform.py:78 assets/serializers/platform.py:104 msgid "Automation" msgstr "オートメーション" @@ -1541,7 +1547,7 @@ msgstr "" msgid "Auto fill" msgstr "自動充填" -#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:94 +#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:102 #: authentication/serializers/connect_token_secret.py:28 #: authentication/serializers/connect_token_secret.py:66 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 @@ -1675,10 +1681,16 @@ msgstr "アカウント収集を有効にする" msgid "Gather accounts method" msgstr "アカウントの収集方法" -#: assets/serializers/platform.py:118 +#: assets/serializers/platform.py:125 msgid "Default Domain" msgstr "デフォルト ドメイン" +#: assets/serializers/platform.py:137 +#, fuzzy +#| msgid "Protocol is required: {}" +msgid "Protocols is required" +msgstr "プロトコルが必要です: {}" + #: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:35 msgid "Test assets connectivity " msgstr "アセット接続のテスト。" @@ -1814,7 +1826,7 @@ msgid "Change password" msgstr "パスワードを変更する" #: audits/const.py:34 settings/serializers/terminal.py:6 -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:156 +#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:161 #: terminal/serializers/session.py:48 msgid "Terminal" msgstr "ターミナル" @@ -1853,7 +1865,7 @@ msgid "Job audit log" msgstr "ジョブ監査ログ" #: audits/models.py:46 audits/models.py:73 audits/models.py:144 -#: terminal/models/session/session.py:39 terminal/models/session/sharing.py:95 +#: terminal/models/session/session.py:39 terminal/models/session/sharing.py:99 msgid "Remote addr" msgstr "リモートaddr" @@ -2685,7 +2697,7 @@ msgid "request new one" msgstr "新しいものを要求する" #: authentication/templates/authentication/_msg_reset_password_code.html:12 -#: terminal/models/session/sharing.py:25 terminal/models/session/sharing.py:79 +#: terminal/models/session/sharing.py:25 terminal/models/session/sharing.py:83 #: users/forms/profile.py:104 users/templates/users/forgot_password.html:65 msgid "Verify code" msgstr "コードの確認" @@ -3187,7 +3199,7 @@ msgstr "選択項目のみエクスポート" msgid "Export filtered: %s" msgstr "検索のエクスポート: %s" -#: common/views/mixins.py:90 +#: common/views/mixins.py:89 #, python-format msgid "User %s view/export secret" msgstr "ユーザー %s がパスワードを閲覧/導き出しました" @@ -3448,7 +3460,7 @@ msgstr "クワーグ" msgid "State" msgstr "状態" -#: ops/models/celery.py:61 terminal/models/session/sharing.py:110 +#: ops/models/celery.py:61 terminal/models/session/sharing.py:114 #: tickets/const.py:25 msgid "Finished" msgstr "終了" @@ -5246,8 +5258,8 @@ msgstr "期限切れです。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" @@ -5268,34 +5280,34 @@ msgid "" " " msgstr "" "\n" -" クリックしてください リンク パスワードの更新\n" +" クリックしてください リンク パスワードの更新\n" " " #: templates/_message.html:43 #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" -" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" +" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" " " #: templates/_message.html:56 #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" +" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -5438,7 +5450,7 @@ msgid "Output" msgstr "出力" #: terminal/backends/command/models.py:24 terminal/models/session/replay.py:9 -#: terminal/models/session/sharing.py:18 terminal/models/session/sharing.py:77 +#: terminal/models/session/sharing.py:18 terminal/models/session/sharing.py:81 #: terminal/templates/terminal/_msg_command_alert.html:10 #: tickets/models/ticket/command_confirm.py:15 msgid "Session" @@ -5479,6 +5491,14 @@ msgstr "一致しない" msgid "Tunnel" msgstr "" +#: terminal/const.py:71 +msgid "Read Only" +msgstr "読み取り専用" + +#: terminal/const.py:72 +msgid "Writable" +msgstr "書き込み可能" + #: terminal/exceptions.py:8 msgid "Bulk create not support" msgstr "一括作成非サポート" @@ -5576,31 +5596,31 @@ msgstr "IP グループ" msgid "Endpoint rule" msgstr "エンドポイントルール" -#: terminal/models/component/status.py:14 +#: terminal/models/component/status.py:15 msgid "Session Online" msgstr "セッションオンライン" -#: terminal/models/component/status.py:15 +#: terminal/models/component/status.py:16 msgid "CPU Load" msgstr "CPUロード" -#: terminal/models/component/status.py:16 +#: terminal/models/component/status.py:17 msgid "Memory Used" msgstr "使用メモリ" -#: terminal/models/component/status.py:17 +#: terminal/models/component/status.py:18 msgid "Disk Used" msgstr "使用済みディスク" -#: terminal/models/component/status.py:18 +#: terminal/models/component/status.py:19 msgid "Connections" msgstr "接続" -#: terminal/models/component/status.py:19 +#: terminal/models/component/status.py:20 msgid "Threads" msgstr "スレッド" -#: terminal/models/component/status.py:20 +#: terminal/models/component/status.py:21 msgid "Boot Time" msgstr "ブート時間" @@ -5609,28 +5629,28 @@ msgid "Default storage" msgstr "デフォルトのストレージ" #: terminal/models/component/storage.py:140 -#: terminal/models/component/terminal.py:85 +#: terminal/models/component/terminal.py:90 msgid "Command storage" msgstr "コマンドストレージ" #: terminal/models/component/storage.py:200 -#: terminal/models/component/terminal.py:86 +#: terminal/models/component/terminal.py:91 msgid "Replay storage" msgstr "再生ストレージ" -#: terminal/models/component/terminal.py:82 +#: terminal/models/component/terminal.py:87 msgid "type" msgstr "タイプ" -#: terminal/models/component/terminal.py:84 terminal/serializers/command.py:51 +#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:51 msgid "Remote Address" msgstr "リモートアドレス" -#: terminal/models/component/terminal.py:87 +#: terminal/models/component/terminal.py:92 msgid "Application User" msgstr "ユーザーの適用" -#: terminal/models/component/terminal.py:158 +#: terminal/models/component/terminal.py:163 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" @@ -5654,7 +5674,7 @@ msgstr "セッション再生をダウンロードできます" msgid "Account id" msgstr "アカウント ID" -#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:100 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:104 msgid "Login from" msgstr "ログイン元" @@ -5690,43 +5710,48 @@ msgstr "セッションアクションのパーマを検証できます" msgid "Expired time (min)" msgstr "期限切れ時間 (分)" -#: terminal/models/session/sharing.py:36 terminal/models/session/sharing.py:82 +#: terminal/models/session/sharing.py:35 terminal/serializers/sharing.py:20 +#: terminal/serializers/sharing.py:52 +msgid "Action permission" +msgstr "アクションパーミッション" + +#: terminal/models/session/sharing.py:40 terminal/models/session/sharing.py:86 msgid "Session sharing" msgstr "セッション共有" -#: terminal/models/session/sharing.py:38 +#: terminal/models/session/sharing.py:42 msgid "Can add super session sharing" msgstr "スーパーセッション共有を追加できます" -#: terminal/models/session/sharing.py:65 +#: terminal/models/session/sharing.py:69 msgid "Link not active" msgstr "リンクがアクティブでない" -#: terminal/models/session/sharing.py:67 +#: terminal/models/session/sharing.py:71 msgid "Link expired" msgstr "リンク期限切れ" -#: terminal/models/session/sharing.py:69 +#: terminal/models/session/sharing.py:73 msgid "User not allowed to join" msgstr "ユーザーはセッションに参加できません" -#: terminal/models/session/sharing.py:86 terminal/serializers/sharing.py:59 +#: terminal/models/session/sharing.py:90 terminal/serializers/sharing.py:71 msgid "Joiner" msgstr "ジョイナー" -#: terminal/models/session/sharing.py:89 +#: terminal/models/session/sharing.py:93 msgid "Date joined" msgstr "参加日" -#: terminal/models/session/sharing.py:92 +#: terminal/models/session/sharing.py:96 msgid "Date left" msgstr "日付が残っています" -#: terminal/models/session/sharing.py:115 +#: terminal/models/session/sharing.py:119 msgid "Session join record" msgstr "セッション参加記録" -#: terminal/models/session/sharing.py:131 +#: terminal/models/session/sharing.py:135 msgid "Invalid verification code" msgstr "検証コードが無効" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a86f2df06..95a5402be 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-03-27 17:00+0800\n" +"POT-Creation-Date: 2023-04-03 18:06+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -291,7 +291,7 @@ msgid "Trigger mode" msgstr "触发模式" #: accounts/models/automations/backup_account.py:97 audits/models.py:172 -#: terminal/models/session/sharing.py:107 xpack/plugins/cloud/models.py:168 +#: terminal/models/session/sharing.py:111 xpack/plugins/cloud/models.py:168 msgid "Reason" msgstr "原因" @@ -431,7 +431,13 @@ msgstr "最后登录地址" msgid "Gather account automation" msgstr "自动化收集账号" -#: accounts/models/automations/gather_account.py:39 +#: accounts/models/automations/gather_account.py:56 +#, fuzzy +#| msgid "Is service account" +msgid "Is sync account" +msgstr "服务账号" + +#: accounts/models/automations/gather_account.py:71 #: accounts/tasks/gather_accounts.py:29 msgid "Gather asset accounts" msgstr "收集账号" @@ -464,7 +470,7 @@ msgstr "账号验证" #: assets/models/group.py:20 assets/models/label.py:18 #: assets/models/platform.py:13 assets/models/platform.py:64 #: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:155 -#: assets/serializers/platform.py:141 +#: assets/serializers/platform.py:148 #: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 @@ -473,7 +479,7 @@ msgstr "账号验证" #: terminal/models/applet/applet.py:27 terminal/models/component/endpoint.py:12 #: terminal/models/component/endpoint.py:90 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:15 -#: terminal/models/component/terminal.py:79 users/forms/profile.py:33 +#: terminal/models/component/terminal.py:84 users/forms/profile.py:33 #: users/models/group.py:13 users/models/user.py:717 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -547,7 +553,7 @@ msgstr "已托管密码" #: accounts/serializers/account/account.py:90 applications/models.py:11 #: assets/models/label.py:21 assets/models/platform.py:65 #: assets/serializers/asset/common.py:131 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:92 assets/serializers/platform.py:142 +#: assets/serializers/platform.py:100 assets/serializers/platform.py:149 #: perms/serializers/user_permission.py:26 settings/models.py:35 #: tickets/models/ticket/apply_application.py:13 msgid "Category" @@ -558,7 +564,7 @@ msgstr "类别" #: 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:66 -#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:91 +#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:99 #: audits/serializers.py:48 #: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 @@ -679,7 +685,7 @@ msgstr "自动化任务执行历史" #: accounts/serializers/automations/change_secret.py:154 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:103 +#: terminal/const.py:59 terminal/models/session/sharing.py:107 #: tickets/views/approve.py:114 msgid "Success" msgstr "成功" @@ -1301,7 +1307,7 @@ msgstr "资产自动化任务" #: assets/models/automations/base.py:112 audits/models.py:177 #: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:183 #: terminal/models/applet/applet.py:157 terminal/models/applet/host.py:108 -#: terminal/models/component/status.py:27 terminal/serializers/applet.py:18 +#: 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 #: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:164 @@ -1496,23 +1502,23 @@ msgstr "元数据" msgid "Internal" msgstr "内置" -#: assets/models/platform.py:71 assets/serializers/platform.py:89 +#: assets/models/platform.py:71 assets/serializers/platform.py:97 msgid "Charset" msgstr "编码" -#: assets/models/platform.py:73 assets/serializers/platform.py:117 +#: assets/models/platform.py:73 assets/serializers/platform.py:124 msgid "Domain enabled" msgstr "启用网域" -#: assets/models/platform.py:75 assets/serializers/platform.py:116 +#: assets/models/platform.py:75 assets/serializers/platform.py:123 msgid "Su enabled" msgstr "启用账号切换" -#: assets/models/platform.py:76 assets/serializers/platform.py:99 +#: assets/models/platform.py:76 assets/serializers/platform.py:106 msgid "Su method" msgstr "账号切换方式" -#: assets/models/platform.py:78 assets/serializers/platform.py:96 +#: assets/models/platform.py:78 assets/serializers/platform.py:104 msgid "Automation" msgstr "自动化" @@ -1531,7 +1537,7 @@ msgstr "资产中批量更新平台,不符合平台类型跳过的资产" msgid "Auto fill" msgstr "自动代填" -#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:94 +#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:102 #: authentication/serializers/connect_token_secret.py:28 #: authentication/serializers/connect_token_secret.py:66 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 @@ -1665,10 +1671,16 @@ msgstr "启用账号收集" msgid "Gather accounts method" msgstr "收集账号方式" -#: assets/serializers/platform.py:118 +#: assets/serializers/platform.py:125 msgid "Default Domain" msgstr "默认网域" +#: assets/serializers/platform.py:137 +#, fuzzy +#| msgid "Protocol is required: {}" +msgid "Protocols is required" +msgstr "协议是必填的: {}" + #: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:35 msgid "Test assets connectivity " msgstr "测试资产可连接性" @@ -1802,7 +1814,7 @@ msgid "Change password" msgstr "改密" #: audits/const.py:34 settings/serializers/terminal.py:6 -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:156 +#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:161 #: terminal/serializers/session.py:48 msgid "Terminal" msgstr "终端" @@ -1841,7 +1853,7 @@ msgid "Job audit log" msgstr "作业审计日志" #: audits/models.py:46 audits/models.py:73 audits/models.py:144 -#: terminal/models/session/session.py:39 terminal/models/session/sharing.py:95 +#: terminal/models/session/session.py:39 terminal/models/session/sharing.py:99 msgid "Remote addr" msgstr "远端地址" @@ -2657,7 +2669,7 @@ msgid "request new one" msgstr "重新申请" #: authentication/templates/authentication/_msg_reset_password_code.html:12 -#: terminal/models/session/sharing.py:25 terminal/models/session/sharing.py:79 +#: terminal/models/session/sharing.py:25 terminal/models/session/sharing.py:83 #: users/forms/profile.py:104 users/templates/users/forgot_password.html:65 msgid "Verify code" msgstr "验证码" @@ -3155,7 +3167,7 @@ msgstr "仅导出选择项" msgid "Export filtered: %s" msgstr "导出搜素: %s" -#: common/views/mixins.py:90 +#: common/views/mixins.py:89 #, python-format msgid "User %s view/export secret" msgstr "用户 %s 查看/导出 了密码" @@ -3411,7 +3423,7 @@ msgstr "其它参数" msgid "State" msgstr "状态" -#: ops/models/celery.py:61 terminal/models/session/sharing.py:110 +#: ops/models/celery.py:61 terminal/models/session/sharing.py:114 #: tickets/const.py:25 msgid "Finished" msgstr "结束" @@ -5177,13 +5189,13 @@ msgstr "过期。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" -" 您的密码已经过期,请点击 链接 更新密码\n" +" 您的密码已经过期,请点击 链接 更新密码\n" " " #: templates/_message.html:30 @@ -5207,8 +5219,8 @@ msgstr "" #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" @@ -5220,13 +5232,13 @@ msgstr "" #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" +" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -5364,7 +5376,7 @@ msgid "Output" msgstr "输出" #: terminal/backends/command/models.py:24 terminal/models/session/replay.py:9 -#: terminal/models/session/sharing.py:18 terminal/models/session/sharing.py:77 +#: terminal/models/session/sharing.py:18 terminal/models/session/sharing.py:81 #: terminal/templates/terminal/_msg_command_alert.html:10 #: tickets/models/ticket/command_confirm.py:15 msgid "Session" @@ -5405,6 +5417,14 @@ msgstr "未匹配" msgid "Tunnel" msgstr "隧道" +#: terminal/const.py:71 +msgid "Read Only" +msgstr "只读" + +#: terminal/const.py:72 +msgid "Writable" +msgstr "读写" + #: terminal/exceptions.py:8 msgid "Bulk create not support" msgstr "不支持批量创建" @@ -5502,31 +5522,31 @@ msgstr "IP 组" msgid "Endpoint rule" msgstr "端点规则" -#: terminal/models/component/status.py:14 +#: terminal/models/component/status.py:15 msgid "Session Online" msgstr "在线会话" -#: terminal/models/component/status.py:15 +#: terminal/models/component/status.py:16 msgid "CPU Load" msgstr "CPU负载" -#: terminal/models/component/status.py:16 +#: terminal/models/component/status.py:17 msgid "Memory Used" msgstr "内存使用" -#: terminal/models/component/status.py:17 +#: terminal/models/component/status.py:18 msgid "Disk Used" msgstr "磁盘使用" -#: terminal/models/component/status.py:18 +#: terminal/models/component/status.py:19 msgid "Connections" msgstr "连接数" -#: terminal/models/component/status.py:19 +#: terminal/models/component/status.py:20 msgid "Threads" msgstr "线程数" -#: terminal/models/component/status.py:20 +#: terminal/models/component/status.py:21 msgid "Boot Time" msgstr "运行时间" @@ -5535,28 +5555,28 @@ msgid "Default storage" msgstr "默认存储" #: terminal/models/component/storage.py:140 -#: terminal/models/component/terminal.py:85 +#: terminal/models/component/terminal.py:90 msgid "Command storage" msgstr "命令存储" #: terminal/models/component/storage.py:200 -#: terminal/models/component/terminal.py:86 +#: terminal/models/component/terminal.py:91 msgid "Replay storage" msgstr "录像存储" -#: terminal/models/component/terminal.py:82 +#: terminal/models/component/terminal.py:87 msgid "type" msgstr "类型" -#: terminal/models/component/terminal.py:84 terminal/serializers/command.py:51 +#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:51 msgid "Remote Address" msgstr "远端地址" -#: terminal/models/component/terminal.py:87 +#: terminal/models/component/terminal.py:92 msgid "Application User" msgstr "应用用户" -#: terminal/models/component/terminal.py:158 +#: terminal/models/component/terminal.py:163 msgid "Can view terminal config" msgstr "可以查看终端配置" @@ -5580,7 +5600,7 @@ msgstr "可以下载会话录像" msgid "Account id" msgstr "账号 ID" -#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:100 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:104 msgid "Login from" msgstr "登录来源" @@ -5616,43 +5636,48 @@ msgstr "可以验证会话动作权限" msgid "Expired time (min)" msgstr "过期时间 (分)" -#: terminal/models/session/sharing.py:36 terminal/models/session/sharing.py:82 +#: terminal/models/session/sharing.py:35 terminal/serializers/sharing.py:20 +#: terminal/serializers/sharing.py:52 +msgid "Action permission" +msgstr "操作权限" + +#: terminal/models/session/sharing.py:40 terminal/models/session/sharing.py:86 msgid "Session sharing" msgstr "会话分享" -#: terminal/models/session/sharing.py:38 +#: terminal/models/session/sharing.py:42 msgid "Can add super session sharing" msgstr "可以创建超级会话分享" -#: terminal/models/session/sharing.py:65 +#: terminal/models/session/sharing.py:69 msgid "Link not active" msgstr "链接失效" -#: terminal/models/session/sharing.py:67 +#: terminal/models/session/sharing.py:71 msgid "Link expired" msgstr "链接过期" -#: terminal/models/session/sharing.py:69 +#: terminal/models/session/sharing.py:73 msgid "User not allowed to join" msgstr "该用户无权加入会话" -#: terminal/models/session/sharing.py:86 terminal/serializers/sharing.py:59 +#: terminal/models/session/sharing.py:90 terminal/serializers/sharing.py:71 msgid "Joiner" msgstr "加入者" -#: terminal/models/session/sharing.py:89 +#: terminal/models/session/sharing.py:93 msgid "Date joined" msgstr "加入日期" -#: terminal/models/session/sharing.py:92 +#: terminal/models/session/sharing.py:96 msgid "Date left" msgstr "结束日期" -#: terminal/models/session/sharing.py:115 +#: terminal/models/session/sharing.py:119 msgid "Session join record" msgstr "会话加入记录" -#: terminal/models/session/sharing.py:131 +#: terminal/models/session/sharing.py:135 msgid "Invalid verification code" msgstr "验证码不正确" diff --git a/apps/terminal/models/session/sharing.py b/apps/terminal/models/session/sharing.py index 061ffad4a..a62e23a85 100644 --- a/apps/terminal/models/session/sharing.py +++ b/apps/terminal/models/session/sharing.py @@ -146,6 +146,7 @@ class SessionJoinRecord(JMSBaseModel, OrgModelMixin): self.date_left = timezone.now() self.is_finished = True self.save() + @property def action_permission(self): return self.sharing.action_permission From 3c076676897b842167b87b5be8c2369b99424482 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:53:37 +0800 Subject: [PATCH 068/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20account=20?= =?UTF-8?q?migrate=20(#10125)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- .../{0010_account_source_id.py => 0011_account_source_id.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/accounts/migrations/{0010_account_source_id.py => 0011_account_source_id.py} (84%) diff --git a/apps/accounts/migrations/0010_account_source_id.py b/apps/accounts/migrations/0011_account_source_id.py similarity index 84% rename from apps/accounts/migrations/0010_account_source_id.py rename to apps/accounts/migrations/0011_account_source_id.py index dc1a5563c..ff9734404 100644 --- a/apps/accounts/migrations/0010_account_source_id.py +++ b/apps/accounts/migrations/0011_account_source_id.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('accounts', '0009_account_usernames_to_ids'), + ('accounts', '0010_gatheraccountsautomation_is_sync_account'), ] operations = [ From 2b8d0a64fbc628454608ae91d8142b9b18bb53d5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 4 Apr 2023 10:31:57 +0800 Subject: [PATCH 069/100] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0100_auto_20220711_1413.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py index cd5732630..87eed5fb0 100644 --- a/apps/assets/migrations/0100_auto_20220711_1413.py +++ b/apps/assets/migrations/0100_auto_20220711_1413.py @@ -49,7 +49,10 @@ def migrate_asset_accounts(apps, schema_editor): account_values.update(auth_book_auth) auth_infos = [] - username = account_values['username'] + username = account_values.get('username') + if not username: + continue + for attr in auth_attrs: secret = account_values.pop(attr, None) if not secret: From 00ec9b6d5a2e06a2bbad23272fcc28c5482b64da Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 4 Apr 2023 11:28:51 +0800 Subject: [PATCH 070/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DLuna=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E7=94=A8=E6=88=B7=E6=8E=88=E6=9D=83=E6=A0=91=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=B1=95=E5=BC=80=E6=89=80=E6=9C=89=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98(=E5=90=8C=E6=AD=A5=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=96=B9=E5=BC=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/mixin.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 245ac22dd..917c72cdf 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -8,6 +8,15 @@ from common.utils import lazyproperty, timeit class SerializeToTreeNodeMixin: + request: Request + + @lazyproperty + def is_sync(self): + sync_paths = ['/api/v1/perms/users/self/nodes/all-with-assets/tree/'] + for p in sync_paths: + if p == self.request.path: + return True + return False @timeit def serialize_nodes(self, nodes: List[Node], with_asset_amount=False): @@ -17,6 +26,16 @@ class SerializeToTreeNodeMixin: else: def _name(node: Node): return node.value + + def _open(node): + if not self.is_sync: + # 异步加载资产树时,默认展开节点 + return True + if not node.parent_key: + return True + else: + return False + data = [ { 'id': node.key, @@ -24,7 +43,7 @@ class SerializeToTreeNodeMixin: 'title': _name(node), 'pId': node.parent_key, 'isParent': True, - 'open': True, + 'open': _open(node), 'meta': { 'data': { "id": node.id, From 55774dae02471170ee35e8b4bac8cbb4875fdfad Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 4 Apr 2023 11:48:56 +0800 Subject: [PATCH 071/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DLuna=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E7=94=A8=E6=88=B7=E6=8E=88=E6=9D=83=E6=A0=91=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E9=97=AE=E9=A2=98(=E5=90=8C=E6=AD=A5=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=96=B9=E5=BC=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/mixin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 917c72cdf..d7cb90121 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -83,6 +83,8 @@ class SerializeToTreeNodeMixin: 'platform_type': asset.platform.type, 'org_name': asset.org_name, 'sftp': asset.platform_id in sftp_enabled_platform, + 'name': asset.name, + 'address': asset.address }, } } From 187c1e3804a06b0bf2bc0ff10a390f57fde1ea0c Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Tue, 4 Apr 2023 11:54:52 +0800 Subject: [PATCH 072/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96winrm?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E7=BD=91=E5=9F=9F=E8=BF=9E=E6=8E=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81ssh=5Fkey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 5 ++++- apps/ops/ansible/inventory.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 1a9f0b8cc..63b490fd9 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -1,11 +1,12 @@ import json import os import shutil +import yaml + from collections import defaultdict from hashlib import md5 from socket import gethostname -import yaml from django.conf import settings from django.utils import timezone from django.utils.translation import gettext as _ @@ -239,10 +240,12 @@ class BasePlaybookManager: jms_asset, jms_gateway = host['jms_asset'], host.get('gateway') if not jms_gateway: continue + server = SSHTunnelForwarder( (jms_gateway['address'], jms_gateway['port']), ssh_username=jms_gateway['username'], ssh_password=jms_gateway['secret'], + ssh_pkey=jms_gateway['private_key_path'], remote_bind_address=(jms_asset['address'], jms_asset['port']) ) try: diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index d98d105e5..2695851bb 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -100,7 +100,8 @@ class JMSInventory: if ansible_connection in ('local', 'winrm'): host['gateway'] = { 'address': gateway.address, 'port': gateway.port, - 'username': gateway.username, 'secret': gateway.password + 'username': gateway.username, 'secret': gateway.password, + 'private_key_path': gateway.private_key_path } host['jms_asset']['port'] = port else: From 211963a0981874329c1c6a500429e8aa21e1d9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Tue, 4 Apr 2023 12:32:54 +0800 Subject: [PATCH 073/100] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=20applet=20?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/automations/deploy_applet_host/playbook.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index bfe772021..b68f6e06c 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -185,7 +185,8 @@ - name: Generate tinkerd component config ansible.windows.win_powershell: - tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }} --ignore-verify-certs {{ IGNORE_VERIFY_CERTS }} + script: | + tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }} --ignore-verify-certs {{ IGNORE_VERIFY_CERTS }} - name: Install tinkerd service ansible.windows.win_powershell: From 7c1e92c7874db21306dbc25837a1c229d94a7fae Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 4 Apr 2023 16:04:44 +0800 Subject: [PATCH 074/100] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=20=E8=B7=B3=E8=BF=87name=E6=A3=80=E6=9F=A5=20(#10136)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/serializers/account/account.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 97bbbfaa7..1d037f594 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -55,11 +55,12 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): self.from_template_if_need(data) self.set_uniq_name_if_need(data, asset) - @staticmethod - def set_uniq_name_if_need(initial_data, asset): + def set_uniq_name_if_need(self, initial_data, asset): name = initial_data.get('name') if not name: name = initial_data.get('username') + if self.instance and self.instance.name == name: + return if Account.objects.filter(name=name, asset=asset).exists(): name = name + '_' + uuid.uuid4().hex[:4] initial_data['name'] = name From 47d088209091a6f44951ee5b39be86af633be348 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:02:58 +0800 Subject: [PATCH 075/100] =?UTF-8?q?perf:=20=E7=94=A8=E6=88=B7=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E7=A0=81=20=E8=AE=BE=E7=BD=AE=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E5=B7=B2=E5=AD=98=E5=9C=A8=20(#10138)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/users/serializers/profile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py index 85cca8c2a..c3ae5b028 100644 --- a/apps/users/serializers/profile.py +++ b/apps/users/serializers/profile.py @@ -58,10 +58,11 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer): class UserUpdateSecretKeySerializer(serializers.ModelSerializer): new_secret_key = EncryptedField(required=True, max_length=128) new_secret_key_again = EncryptedField(required=True, max_length=128) + has_secret_key = serializers.BooleanField(read_only=True, source='secret_key') class Meta: model = User - fields = ['new_secret_key', 'new_secret_key_again'] + fields = ['has_secret_key', 'new_secret_key', 'new_secret_key_again'] def validate(self, values): new_secret_key = values.get('new_secret_key', '') From fb1978a40b927366b7dd44d74c97ae154d3928ec Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 6 Apr 2023 10:31:41 +0800 Subject: [PATCH 076/100] fix: terminal status (#10142) Co-authored-by: feng <1304903146@qq.com> --- apps/terminal/models/component/status.py | 2 +- apps/terminal/models/component/terminal.py | 12 +++++------- apps/terminal/utils/components.py | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/terminal/models/component/status.py b/apps/terminal/models/component/status.py index 279d6ac95..d273b9dac 100644 --- a/apps/terminal/models/component/status.py +++ b/apps/terminal/models/component/status.py @@ -53,7 +53,7 @@ class Status(models.Model): def save(self, force_insert=False, force_update=False, using=None, update_fields=None): - self.terminal.set_alive(ttl=120) + self.terminal.set_alive(ttl=60 * 3) return self.save_to_cache() def save_to_cache(self): diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index df09df680..88c2c6154 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,5 +1,3 @@ -import time - from django.conf import settings from django.core.cache import cache from django.db import models @@ -11,6 +9,7 @@ from common.utils import get_logger, lazyproperty from orgs.utils import tmp_to_root_org from terminal.const import TerminalType as TypeChoices from users.models import User +from .status import Status from ..session import Session logger = get_logger(__file__) @@ -23,7 +22,7 @@ class TerminalStatusMixin: @lazyproperty def last_stat(self): - return self.status_set.order_by('date_created').last() + return Status.get_terminal_latest_stat(self) @lazyproperty def load(self): @@ -32,11 +31,10 @@ class TerminalStatusMixin: @property def is_alive(self): - if not self.last_stat: - return False - return time.time() - self.last_stat.date_created.timestamp() < 150 + key = self.ALIVE_KEY.format(self.id) + return cache.get(key, False) - def set_alive(self, ttl=120): + def set_alive(self, ttl=60 * 3): key = self.ALIVE_KEY.format(self.id) cache.set(key, True, ttl) diff --git a/apps/terminal/utils/components.py b/apps/terminal/utils/components.py index 59d021186..a3de26322 100644 --- a/apps/terminal/utils/components.py +++ b/apps/terminal/utils/components.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -import time from itertools import groupby from common.utils import get_logger @@ -40,7 +39,7 @@ class ComputeLoadUtil: @classmethod def compute_load(cls, stat): - if not stat or time.time() - stat.date_created.timestamp() > 150: + if not stat: return ComponentLoad.offline system_status_values = cls._compute_system_stat_status(stat).values() if ComponentLoad.critical in system_status_values: From e34fbce082d214254eca317972b9e42890c30eb8 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 6 Apr 2023 19:53:01 +0800 Subject: [PATCH 077/100] perf: patch account 400 (#10153) Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/serializers/account/account.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 1d037f594..d5b93dcd8 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -57,6 +57,8 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): def set_uniq_name_if_need(self, initial_data, asset): name = initial_data.get('name') + if name is None: + return if not name: name = initial_data.get('username') if self.instance and self.instance.name == name: From ec2c8538d9da9aaa67dab8258a577ad8c115a5e2 Mon Sep 17 00:00:00 2001 From: halo Date: Wed, 5 Apr 2023 20:36:18 +0800 Subject: [PATCH 078/100] =?UTF-8?q?fix:=20=E9=A6=96=E6=AC=A1=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E5=BC=BA=E5=88=B6=E5=BC=80=E5=90=AFMFA=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/serializers/profile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py index c3ae5b028..7502a7525 100644 --- a/apps/users/serializers/profile.py +++ b/apps/users/serializers/profile.py @@ -115,6 +115,7 @@ class UserProfileSerializer(UserSerializer): MFA_LEVEL_CHOICES = ( (0, _('Disable')), (1, _('Enable')), + (2, _("Force enable")), ) public_key_comment = serializers.CharField( source='get_public_key_comment', required=False, read_only=True, max_length=128 From 7833433d5f76d25fb7e28b4a2cbd1afbf6d1c012 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Fri, 7 Apr 2023 21:25:26 +0800 Subject: [PATCH 079/100] =?UTF-8?q?feat:=20=E6=89=8B=E6=9C=BA=E5=8F=B7?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=80=89=E6=8B=A9=E5=8C=BA=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/validators.py | 6 ++++-- apps/users/serializers/user.py | 10 ++++++++++ requirements/requirements.txt | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/common/validators.py b/apps/common/validators.py index 4be90d855..c77a03581 100644 --- a/apps/common/validators.py +++ b/apps/common/validators.py @@ -2,6 +2,8 @@ # import re +import phonenumbers + from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from rest_framework.validators import ( @@ -42,9 +44,9 @@ class NoSpecialChars: class PhoneValidator: - pattern = re.compile(r"^1[3456789]\d{9}$") message = _('The mobile phone number format is incorrect') def __call__(self, value): - if not self.pattern.match(value): + phone = phonenumbers.parse(value) + if not phonenumbers.is_valid_number(phone): raise serializers.ValidationError(self.message) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 962fbcd24..5d239820c 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # +import phonenumbers + from functools import partial from django.utils.translation import ugettext_lazy as _ @@ -222,6 +224,14 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer attrs.pop("password_strategy", None) return attrs + def to_representation(self, instance): + data = super().to_representation(instance) + phone = phonenumbers.parse(data['phone'], 'CN') + data['phone'] = { + 'code': '+%s' % phone.country_code, 'phone': phone.national_number + } + return data + def save_and_set_custom_m2m_fields(self, validated_data, save_handler, created): m2m_values = {} for f, default_roles in self.custom_m2m_fields.items(): diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 6457f2104..e01baa999 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -29,6 +29,7 @@ pycparser==2.21 cryptography==38.0.4 pycryptodome==3.15.0 pycryptodomex==3.15.0 +phonenumbers==8.13.8 gmssl==3.2.1 itsdangerous==1.1.0 pyotp==2.6.0 From cec176cc33c152b79fe23e1df235ed693292a1a6 Mon Sep 17 00:00:00 2001 From: wulabing Date: Sun, 9 Apr 2023 00:45:11 +0800 Subject: [PATCH 080/100] fix notifications.py fix ops.tasks.check_server_performance_period AttributeError: type object 'Status' has no attribute 'get_terminal_latest_stat' --- apps/ops/notifications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py index 86c7ab188..b0c4906e4 100644 --- a/apps/ops/notifications.py +++ b/apps/ops/notifications.py @@ -6,7 +6,8 @@ from notifications.notifications import SystemMessage from notifications.models import SystemMsgSubscription from users.models import User from notifications.backends import BACKEND -from terminal.models import Status, Terminal +from terminal.models.component.status import Status +from terminal.models import Terminal __all__ = ('ServerPerformanceMessage', 'ServerPerformanceCheckUtil') From 1248458451fc209be67edb53aafde3b5cf31d51a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 10 Apr 2023 10:57:44 +0800 Subject: [PATCH 081/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20choices=20(#10151)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 支持自定义类型资产 * perf: 改名前 * perf: 优化支持 choices * perf: 优化自定义资产 * perf: 优化资产的详情 * perf: 修改完成自定义平台和资产 --------- Co-authored-by: ibuler Co-authored-by: Jiangjie.Bai --- apps/accounts/api/account/task.py | 4 +- apps/accounts/serializers/account/account.py | 2 +- apps/assets/api/asset/__init__.py | 7 +- apps/assets/api/asset/asset.py | 13 +- apps/assets/api/asset/custom.py | 16 + apps/assets/api/asset/host.py | 17 +- apps/assets/api/platform.py | 2 +- .../automations/gather_facts/manager.py | 4 +- apps/assets/automations/methods.py | 11 +- apps/assets/const/base.py | 37 +- apps/assets/const/category.py | 6 +- apps/assets/const/custom.py | 56 +++ apps/assets/const/protocol.py | 2 +- apps/assets/const/types.py | 38 +- .../migrations/0111_auto_20230321_1633.py | 6 +- .../migrations/0112_auto_20230404_1631.py | 45 ++ .../0112_platformprotocol_public.py | 18 - apps/assets/models/asset/__init__.py | 5 +- apps/assets/models/asset/common.py | 17 +- apps/assets/models/asset/custom.py | 8 + apps/assets/models/platform.py | 12 +- apps/assets/serializers/asset/__init__.py | 6 +- apps/assets/serializers/asset/common.py | 102 +++- apps/assets/serializers/asset/custom.py | 9 + apps/assets/serializers/asset/database.py | 4 +- apps/assets/serializers/asset/host.py | 24 +- .../assets/serializers/asset/info/gathered.py | 23 + apps/assets/serializers/asset/info/spec.py | 24 + apps/assets/serializers/gateway.py | 2 +- apps/assets/serializers/platform.py | 77 ++- apps/assets/signal_handlers/asset.py | 6 +- apps/assets/urls/api_urls.py | 1 + .../serializers/connect_token_secret.py | 21 +- apps/common/serializers/dynamic.py | 54 +++ apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 458 +++++++++++------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 449 +++++++++-------- apps/terminal/api/applet/applet.py | 8 +- apps/terminal/models/applet/applet.py | 39 +- apps/terminal/models/component/terminal.py | 3 + 41 files changed, 1061 insertions(+), 583 deletions(-) create mode 100644 apps/assets/api/asset/custom.py create mode 100644 apps/assets/const/custom.py create mode 100644 apps/assets/migrations/0112_auto_20230404_1631.py delete mode 100644 apps/assets/migrations/0112_platformprotocol_public.py create mode 100644 apps/assets/models/asset/custom.py create mode 100644 apps/assets/serializers/asset/custom.py create mode 100644 apps/assets/serializers/asset/info/gathered.py create mode 100644 apps/assets/serializers/asset/info/spec.py create mode 100644 apps/common/serializers/dynamic.py diff --git a/apps/accounts/api/account/task.py b/apps/accounts/api/account/task.py index a042323f5..2f3f11dae 100644 --- a/apps/accounts/api/account/task.py +++ b/apps/accounts/api/account/task.py @@ -31,8 +31,8 @@ class AccountsTaskCreateAPI(CreateAPIView): else: account = accounts[0] asset = account.asset - if not asset.auto_info['ansible_enabled'] or \ - not asset.auto_info['ping_enabled']: + if not asset.auto_config['ansible_enabled'] or \ + not asset.auto_config['ping_enabled']: raise NotSupportedTemporarilyError() task = verify_accounts_connectivity_task.delay(account_ids) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index d5b93dcd8..0a1cf3f83 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -158,7 +158,7 @@ class AccountAssetSerializer(serializers.ModelSerializer): class Meta: model = Asset - fields = ['id', 'name', 'address', 'type', 'category', 'platform', 'auto_info'] + fields = ['id', 'name', 'address', 'type', 'category', 'platform', 'auto_config'] def to_internal_value(self, data): if isinstance(data, dict): diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py index c20e44573..0f1d81825 100644 --- a/apps/assets/api/asset/__init__.py +++ b/apps/assets/api/asset/__init__.py @@ -1,7 +1,8 @@ from .asset import * -from .host import * -from .database import * -from .web import * from .cloud import * +from .custom import * +from .database import * from .device import * +from .host import * from .permission import * +from .web import * diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 1e887e572..aa20bded9 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -102,14 +102,13 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): ("platform", serializers.PlatformSerializer), ("suggestion", serializers.MiniAssetSerializer), ("gateways", serializers.GatewaySerializer), - ("spec_info", serializers.SpecSerializer), ) rbac_perms = ( ("match", "assets.match_asset"), ("platform", "assets.view_platform"), ("gateways", "assets.view_gateway"), ("spec_info", "assets.view_asset"), - ("info", "assets.view_asset"), + ("gathered_info", "assets.view_asset"), ) extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend] skip_assets = [] @@ -128,11 +127,6 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): serializer = super().get_serializer(instance=asset.platform) return Response(serializer.data) - @action(methods=["GET"], detail=True, url_path="spec-info") - def spec_info(self, *args, **kwargs): - asset = super().get_object() - return Response(asset.spec_info) - @action(methods=["GET"], detail=True, url_path="gateways") def gateways(self, *args, **kwargs): asset = self.get_object() @@ -163,6 +157,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): continue self.skip_assets.append(asset) return bulk_data + def bulk_update(self, request, *args, **kwargs): bulk_data = self.filter_bulk_update_data() request._full_data = bulk_data @@ -182,8 +177,8 @@ class AssetsTaskMixin: task = update_assets_hardware_info_manual(assets) else: asset = assets[0] - if not asset.auto_info['ansible_enabled'] or \ - not asset.auto_info['ping_enabled']: + if not asset.auto_config['ansible_enabled'] or \ + not asset.auto_config['ping_enabled']: raise NotSupportedTemporarilyError() task = test_assets_connectivity_manual(assets) return task diff --git a/apps/assets/api/asset/custom.py b/apps/assets/api/asset/custom.py new file mode 100644 index 000000000..ca5058ed6 --- /dev/null +++ b/apps/assets/api/asset/custom.py @@ -0,0 +1,16 @@ +from assets.models import Custom, Asset +from assets.serializers import CustomSerializer + +from .asset import AssetViewSet + +__all__ = ['CustomViewSet'] + + +class CustomViewSet(AssetViewSet): + model = Custom + perm_model = Asset + + def get_serializer_classes(self): + serializer_classes = super().get_serializer_classes() + serializer_classes['default'] = CustomSerializer + return serializer_classes diff --git a/apps/assets/api/asset/host.py b/apps/assets/api/asset/host.py index b92448bfb..d2ddc954d 100644 --- a/apps/assets/api/asset/host.py +++ b/apps/assets/api/asset/host.py @@ -1,8 +1,5 @@ -from rest_framework.decorators import action -from rest_framework.response import Response - from assets.models import Host, Asset -from assets.serializers import HostSerializer, HostInfoSerializer +from assets.serializers import HostSerializer from .asset import AssetViewSet __all__ = ['HostViewSet'] @@ -15,16 +12,4 @@ class HostViewSet(AssetViewSet): def get_serializer_classes(self): serializer_classes = super().get_serializer_classes() serializer_classes['default'] = HostSerializer - serializer_classes['info'] = HostInfoSerializer return serializer_classes - - @action(methods=["GET"], detail=True, url_path="info") - def info(self, *args, **kwargs): - asset = super().get_object() - serializer = self.get_serializer(asset.info) - data = serializer.data - data['asset'] = { - 'id': asset.id, 'name': asset.name, - 'address': asset.address - } - return Response(data) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index be9456ff7..5d5d4523c 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -23,7 +23,7 @@ class AssetPlatformViewSet(JMSModelViewSet): def get_queryset(self): queryset = super().get_queryset() - queryset = queryset.filter(type__in=AllTypes.get_types()) + queryset = queryset.filter(type__in=AllTypes.get_types_values()) return queryset def get_object(self): diff --git a/apps/assets/automations/gather_facts/manager.py b/apps/assets/automations/gather_facts/manager.py index afd5ce523..e33d97617 100644 --- a/apps/assets/automations/gather_facts/manager.py +++ b/apps/assets/automations/gather_facts/manager.py @@ -29,7 +29,7 @@ class GatherFactsManager(BasePlaybookManager): asset = self.host_asset_mapper.get(host) if asset and info: info = self.format_asset_info(asset.type, info) - asset.info = info - asset.save(update_fields=['info']) + asset.gathered_info = info + asset.save(update_fields=['gathered_info']) else: logger.error("Not found info: {}".format(host)) diff --git a/apps/assets/automations/methods.py b/apps/assets/automations/methods.py index 86fcb775c..e1e12fb7f 100644 --- a/apps/assets/automations/methods.py +++ b/apps/assets/automations/methods.py @@ -1,8 +1,9 @@ -import os -import yaml import json +import os from functools import partial +import yaml + def check_platform_method(manifest, manifest_path): required_keys = ['category', 'method', 'name', 'id', 'type'] @@ -46,12 +47,12 @@ def filter_key(manifest, attr, value): return value in manifest_value or 'all' in manifest_value -def filter_platform_methods(category, tp, method=None, methods=None): +def filter_platform_methods(category, tp_name, method=None, methods=None): methods = platform_automation_methods if methods is None else methods if category: methods = filter(partial(filter_key, attr='category', value=category), methods) - if tp: - methods = filter(partial(filter_key, attr='type', value=tp), methods) + if tp_name: + methods = filter(partial(filter_key, attr='type', value=tp_name), methods) if method: methods = filter(lambda x: x['method'] == method, methods) return methods diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index 4b147f8ef..c90ab7320 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -4,6 +4,15 @@ from jumpserver.utils import has_valid_xpack_license from .protocol import Protocol +class Type: + def __init__(self, label, value): + self.label = label + self.value = value + + def __str__(self): + return self.value + + class BaseType(TextChoices): """ 约束应该考虑代是对平台对限制,避免多余对选项,如: mysql 开启 ssh, @@ -22,7 +31,7 @@ class BaseType(TextChoices): protocols_default = protocols.pop('*', {}) automation_default = automation.pop('*', {}) - for k, v in cls.choices: + for k, v in cls.get_choices(): tp_base = {**base_default, **base.get(k, {})} tp_auto = {**automation_default, **automation.get(k, {})} tp_protocols = {**protocols_default, **protocols.get(k, {})} @@ -37,8 +46,12 @@ class BaseType(TextChoices): choices = protocol.get('choices', []) if choices == '__self__': choices = [tp] - protocols = [{'name': name, **settings.get(name, {})} for name in choices] - protocols[0]['default'] = True + protocols = [ + {'name': name, **settings.get(name, {})} + for name in choices + ] + if protocols: + protocols[0]['default'] = True return protocols @classmethod @@ -58,21 +71,21 @@ class BaseType(TextChoices): raise NotImplementedError @classmethod - def get_community_types(cls): - raise NotImplementedError + def _get_choices_to_types(cls): + choices = cls.get_choices() + return [Type(label, value) for value, label in choices] @classmethod def get_types(cls): - tps = [tp for tp in cls] + tps = cls._get_choices_to_types() if not has_valid_xpack_license(): tps = cls.get_community_types() return tps + @classmethod + def get_community_types(cls): + return cls._get_choices_to_types() + @classmethod def get_choices(cls): - tps = cls.get_types() - cls_choices = cls.choices - return [ - choice for choice in cls_choices - if choice[0] in tps - ] + return cls.choices diff --git a/apps/assets/const/category.py b/apps/assets/const/category.py index e14867d69..8c4d387d8 100644 --- a/apps/assets/const/category.py +++ b/apps/assets/const/category.py @@ -3,7 +3,6 @@ from django.utils.translation import gettext_lazy as _ from common.db.models import ChoicesMixin - __all__ = ['Category'] @@ -13,13 +12,10 @@ class Category(ChoicesMixin, models.TextChoices): DATABASE = 'database', _("Database") CLOUD = 'cloud', _("Cloud service") WEB = 'web', _("Web") + CUSTOM = 'custom', _("Custom type") @classmethod def filter_choices(cls, category): _category = getattr(cls, category.upper(), None) choices = [(_category.value, _category.label)] if _category else cls.choices return choices - - - - diff --git a/apps/assets/const/custom.py b/apps/assets/const/custom.py new file mode 100644 index 000000000..be9cdcc92 --- /dev/null +++ b/apps/assets/const/custom.py @@ -0,0 +1,56 @@ +from .base import BaseType + + +class CustomTypes(BaseType): + @classmethod + def get_choices(cls): + types = cls.get_custom_platforms().values_list('type', flat=True).distinct() + return [(t, t) for t in types] + + @classmethod + def _get_base_constrains(cls) -> dict: + return { + '*': { + 'charset_enabled': False, + 'domain_enabled': False, + 'su_enabled': False, + }, + } + + @classmethod + def _get_automation_constrains(cls) -> dict: + constrains = { + '*': { + 'ansible_enabled': False, + 'ansible_config': {}, + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_secret_enabled': False, + 'push_account_enabled': False, + 'gather_accounts_enabled': False, + } + } + return constrains + + @classmethod + def _get_protocol_constrains(cls) -> dict: + constrains = {} + for platform in cls.get_custom_platforms(): + choices = list(platform.protocols.values_list('name', flat=True)) + if platform.type in constrains: + choices = constrains[platform.type]['choices'] + choices + constrains[platform.type] = {'choices': choices} + return constrains + + @classmethod + def internal_platforms(cls): + return { + # cls.PUBLIC: [], + # cls.PRIVATE: [{'name': 'Vmware-vSphere'}], + # cls.K8S: [{'name': 'Kubernetes'}], + } + + @classmethod + def get_custom_platforms(cls): + from assets.models import Platform + return Platform.objects.filter(category='custom') diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index eef3269d3..a0b7b7ec7 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -141,6 +141,6 @@ class Protocol(ChoicesMixin, models.TextChoices): def protocol_secret_types(cls): settings = cls.settings() return { - protocol: settings[protocol]['secret_types'] + protocol: settings[protocol]['secret_types'] or ['password'] for protocol in cls.settings() } diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 3f2335ceb..a73b967b2 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext as _ from common.db.models import ChoicesMixin from .category import Category from .cloud import CloudTypes +from .custom import CustomTypes from .database import DatabaseTypes from .device import DeviceTypes from .host import HostTypes @@ -16,7 +17,7 @@ class AllTypes(ChoicesMixin): choices: list includes = [ HostTypes, DeviceTypes, DatabaseTypes, - CloudTypes, WebTypes, + CloudTypes, WebTypes, CustomTypes ] _category_constrains = {} @@ -24,22 +25,29 @@ class AllTypes(ChoicesMixin): def choices(cls): choices = [] for tp in cls.includes: - choices.extend(tp.choices) + choices.extend(tp.get_choices()) return choices + @classmethod + def get_choices(cls): + return cls.choices() + @classmethod def filter_choices(cls, category): - choices = dict(cls.category_types()).get(category, cls).choices + choices = dict(cls.category_types()).get(category, cls).get_choices() return choices() if callable(choices) else choices @classmethod - def get_constraints(cls, category, tp): + def get_constraints(cls, category, tp_name): + if not isinstance(tp_name, str): + tp_name = tp_name.value + types_cls = dict(cls.category_types()).get(category) if not types_cls: return {} type_constraints = types_cls.get_constrains() - constraints = type_constraints.get(tp, {}) - cls.set_automation_methods(category, tp, constraints) + constraints = type_constraints.get(tp_name, {}) + cls.set_automation_methods(category, tp_name, constraints) return constraints @classmethod @@ -56,7 +64,7 @@ class AllTypes(ChoicesMixin): return asset_methods + account_methods @classmethod - def set_automation_methods(cls, category, tp, constraints): + def set_automation_methods(cls, category, tp_name, constraints): from assets.automations import filter_platform_methods automation = constraints.get('automation', {}) automation_methods = {} @@ -66,7 +74,7 @@ class AllTypes(ChoicesMixin): continue item_name = item.replace('_enabled', '') methods = filter_platform_methods( - category, tp, item_name, methods=platform_automation_methods + category, tp_name, item_name, methods=platform_automation_methods ) methods = [{'name': m['name'], 'id': m['id']} for m in methods] automation_methods[item_name + '_methods'] = methods @@ -113,7 +121,7 @@ class AllTypes(ChoicesMixin): @classmethod def grouped_choices(cls): - grouped_types = [(str(ca), tp.choices) for ca, tp in cls.category_types()] + grouped_types = [(str(ca), tp.get_choices()) for ca, tp in cls.category_types()] return grouped_types @classmethod @@ -138,14 +146,20 @@ class AllTypes(ChoicesMixin): (Category.DATABASE, DatabaseTypes), (Category.CLOUD, CloudTypes), (Category.WEB, WebTypes), + (Category.CUSTOM, CustomTypes), ) @classmethod def get_types(cls): - tps = [] + choices = [] for i in dict(cls.category_types()).values(): - tps.extend(i.get_types()) - return tps + choices.extend(i.get_types()) + return choices + + @classmethod + def get_types_values(cls): + choices = cls.get_types() + return [c.value for c in choices] @staticmethod def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): diff --git a/apps/assets/migrations/0111_auto_20230321_1633.py b/apps/assets/migrations/0111_auto_20230321_1633.py index fce00cc98..52dbc1971 100644 --- a/apps/assets/migrations/0111_auto_20230321_1633.py +++ b/apps/assets/migrations/0111_auto_20230321_1633.py @@ -28,7 +28,6 @@ def migrate_internal_platforms(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ('assets', '0110_auto_20230315_1741'), ] @@ -39,6 +38,11 @@ class Migration(migrations.Migration): name='primary', field=models.BooleanField(default=False, verbose_name='Primary'), ), + migrations.AddField( + model_name='platformprotocol', + name='public', + field=models.BooleanField(default=True, verbose_name='Public'), + ), migrations.RunPython(migrate_platform_charset), migrations.RunPython(migrate_platform_protocol_primary), migrations.RunPython(migrate_internal_platforms), diff --git a/apps/assets/migrations/0112_auto_20230404_1631.py b/apps/assets/migrations/0112_auto_20230404_1631.py new file mode 100644 index 000000000..cf1c6268a --- /dev/null +++ b/apps/assets/migrations/0112_auto_20230404_1631.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2.17 on 2023-04-04 08:31 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0111_auto_20230321_1633'), + ] + + operations = [ + migrations.CreateModel( + name='Custom', + fields=[ + ('asset_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='assets.asset')), + ], + options={ + 'verbose_name': 'Custom asset', + }, + bases=('assets.asset',), + ), + migrations.AddField( + model_name='platform', + name='custom_fields', + field=models.JSONField(default=list, null=True, verbose_name='Custom fields'), + ), + migrations.AddField( + model_name='asset', + name='custom_info', + field=models.JSONField(default=dict, verbose_name='Custom info'), + ), + migrations.RenameField( + model_name='asset', + old_name='info', + new_name='gathered_info', + ), + migrations.AlterField( + model_name='asset', + name='gathered_info', + field=models.JSONField(blank=True, default=dict, verbose_name='Gathered info'), + ), + ] diff --git a/apps/assets/migrations/0112_platformprotocol_public.py b/apps/assets/migrations/0112_platformprotocol_public.py deleted file mode 100644 index 917686bc6..000000000 --- a/apps/assets/migrations/0112_platformprotocol_public.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.17 on 2023-03-24 03:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0111_auto_20230321_1633'), - ] - - operations = [ - migrations.AddField( - model_name='platformprotocol', - name='public', - field=models.BooleanField(default=True, verbose_name='Public'), - ), - ] diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py index 793df7455..0004bfbb5 100644 --- a/apps/assets/models/asset/__init__.py +++ b/apps/assets/models/asset/__init__.py @@ -1,6 +1,7 @@ +from .cloud import * from .common import * -from .host import * +from .custom import * from .database import * from .device import * +from .host import * from .web import * -from .cloud import * diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 8643f4a6c..40874915f 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -108,7 +108,8 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) - info = models.JSONField(verbose_name=_('Info'), default=dict, blank=True) # 资产的一些信息,如 硬件信息 + gathered_info = models.JSONField(verbose_name=_('Gathered info'), default=dict, blank=True) # 资产的一些信息,如 硬件信息 + custom_info = models.JSONField(verbose_name=_('Custom info'), default=dict) objects = AssetManager.from_queryset(AssetQuerySet)() @@ -148,20 +149,26 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): return self.get_spec_values(instance, spec_fields) @lazyproperty - def auto_info(self): + def auto_config(self): platform = self.platform automation = self.platform.automation - return { + auto_config = { 'su_enabled': platform.su_enabled, - 'ping_enabled': automation.ping_enabled, 'domain_enabled': platform.domain_enabled, + 'ansible_enabled': False + } + if not automation: + return auto_config + auto_config.update({ + 'ping_enabled': automation.ping_enabled, 'ansible_enabled': automation.ansible_enabled, 'push_account_enabled': automation.push_account_enabled, 'gather_facts_enabled': automation.gather_facts_enabled, 'change_secret_enabled': automation.change_secret_enabled, 'verify_account_enabled': automation.verify_account_enabled, 'gather_accounts_enabled': automation.gather_accounts_enabled, - } + }) + return auto_config def get_target_ip(self): return self.address diff --git a/apps/assets/models/asset/custom.py b/apps/assets/models/asset/custom.py new file mode 100644 index 000000000..c3e4263ba --- /dev/null +++ b/apps/assets/models/asset/custom.py @@ -0,0 +1,8 @@ +from django.utils.translation import gettext_lazy as _ + +from .common import Asset + + +class Custom(Asset): + class Meta: + verbose_name = _("Custom asset") diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 7f57c01d7..08cd7e009 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -24,7 +24,7 @@ class PlatformProtocol(models.Model): @property def secret_types(self): - return Protocol.settings().get(self.name, {}).get('secret_types') + return Protocol.settings().get(self.name, {}).get('secret_types', ['password']) class PlatformAutomation(models.Model): @@ -69,14 +69,18 @@ class Platform(JMSBaseModel): internal = models.BooleanField(default=False, verbose_name=_("Internal")) # 资产有关的 charset = models.CharField( - default=CharsetChoices.utf8, choices=CharsetChoices.choices, max_length=8, verbose_name=_("Charset") + default=CharsetChoices.utf8, choices=CharsetChoices.choices, + max_length=8, verbose_name=_("Charset") ) domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) # 账号有关的 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Su method")) - automation = models.OneToOneField(PlatformAutomation, on_delete=models.CASCADE, related_name='platform', - blank=True, null=True, verbose_name=_("Automation")) + automation = models.OneToOneField( + PlatformAutomation, on_delete=models.CASCADE, related_name='platform', + blank=True, null=True, verbose_name=_("Automation") + ) + custom_fields = models.JSONField(null=True, default=list, verbose_name=_("Custom fields")) @property def type_constraints(self): diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py index 12f1eb66c..8e3e14cf3 100644 --- a/apps/assets/serializers/asset/__init__.py +++ b/apps/assets/serializers/asset/__init__.py @@ -1,6 +1,8 @@ +# No pass +from .cloud import * from .common import * -from .host import * +from .custom import * from .database import * from .device import * -from .cloud import * +from .host import * from .web import * diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 22329b22a..12c127dae 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # +import re + from django.db.models import F from django.db.transaction import atomic from django.utils.translation import ugettext_lazy as _ @@ -8,7 +10,9 @@ from rest_framework import serializers from accounts.models import Account from accounts.serializers import AccountSerializer -from common.serializers import WritableNestedModelSerializer, SecretReadableMixin, CommonModelSerializer +from common.serializers import WritableNestedModelSerializer, SecretReadableMixin, CommonModelSerializer, \ + MethodSerializer +from common.serializers.dynamic import create_serializer_class from common.serializers.fields import LabeledChoiceField from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ...const import Category, AllTypes @@ -18,9 +22,11 @@ __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', 'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer', 'AssetDetailSerializer', 'DetailMixin', 'AssetAccountSerializer', - 'AccountSecretSerializer', 'SpecSerializer' + 'AccountSecretSerializer', ] +uuid_pattern = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + class AssetProtocolsSerializer(serializers.ModelSerializer): port = serializers.IntegerField(required=False, allow_null=True, max_value=65535, min_value=1) @@ -83,44 +89,32 @@ class AccountSecretSerializer(SecretReadableMixin, CommonModelSerializer): } -class SpecSerializer(serializers.Serializer): - # 数据库 - db_name = serializers.CharField(label=_("Database"), max_length=128, required=False) - use_ssl = serializers.BooleanField(label=_("Use SSL"), required=False) - allow_invalid_cert = serializers.BooleanField(label=_("Allow invalid cert"), required=False) - # Web - autofill = serializers.CharField(label=_("Auto fill"), required=False) - username_selector = serializers.CharField(label=_("Username selector"), required=False) - password_selector = serializers.CharField(label=_("Password selector"), required=False) - submit_selector = serializers.CharField(label=_("Submit selector"), required=False) - script = serializers.JSONField(label=_("Script"), required=False) - - class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSerializer): 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')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=()) - accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, label=_('Account')) + accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Account')) nodes_display = serializers.ListField(read_only=False, required=False, label=_("Node path")) + custom_info = MethodSerializer(label=_('Custom info')) class Meta: model = Asset fields_mini = ['id', 'name', 'address'] - fields_small = fields_mini + ['is_active', 'comment'] + fields_small = fields_mini + ['custom_info', 'is_active', 'comment'] fields_fk = ['domain', 'platform'] fields_m2m = [ 'nodes', 'labels', 'protocols', - 'nodes_display', 'accounts' + 'nodes_display', 'accounts', ] read_only_fields = [ - 'category', 'type', 'connectivity', 'auto_info', + 'category', 'type', 'connectivity', 'auto_config', 'date_verified', 'created_by', 'date_created', ] fields = fields_small + fields_fk + fields_m2m + read_only_fields - fields_unexport = ['auto_info'] + fields_unexport = ['auto_config'] extra_kwargs = { - 'auto_info': {'label': _('Auto info')}, + 'auto_config': {'label': _('Auto info')}, 'name': {'label': _("Name")}, 'address': {'label': _('Address')}, 'nodes_display': {'label': _('Node path')}, @@ -170,6 +164,36 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali .annotate(type=F("platform__type")) return queryset + def get_custom_info_serializer(self): + request = self.context.get('request') + default_field = serializers.DictField(required=False, label=_('Custom info')) + + if not request: + return default_field + + if self.instance and isinstance(self.instance, list): + return default_field + + if not self.instance and uuid_pattern.findall(request.path): + pk = uuid_pattern.findall(request.path)[0] + self.instance = Asset.objects.filter(id=pk).first() + + platform = None + if self.instance: + platform = self.instance.platform + elif request.query_params.get('platform'): + platform_id = request.query_params.get('platform') + platform_id = int(platform_id) if platform_id.isdigit() else 0 + platform = Platform.objects.filter(id=platform_id).first() + + if not platform: + return default_field + custom_fields = platform.custom_fields + if not custom_fields: + return default_field + name = platform.name.title() + 'CustomSerializer' + return create_serializer_class(name, custom_fields)() + @staticmethod def perform_nodes_display_create(instance, nodes_display): if not nodes_display: @@ -276,16 +300,46 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali class DetailMixin(serializers.Serializer): accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) - spec_info = serializers.DictField(label=_('Spec info'), read_only=True) - auto_info = serializers.DictField(read_only=True, label=_('Auto info')) + spec_info = MethodSerializer(label=_('Spec info'), read_only=True) + gathered_info = MethodSerializer(label=_('Gathered info'), read_only=True) + auto_config = serializers.DictField(read_only=True, label=_('Auto info')) + + def get_instance(self): + request = self.context.get('request') + if not self.instance and uuid_pattern.findall(request.path): + pk = uuid_pattern.findall(request.path)[0] + self.instance = Asset.objects.filter(id=pk).first() + return self.instance def get_field_names(self, declared_fields, info): names = super().get_field_names(declared_fields, info) names.extend([ - 'accounts', 'info', 'spec_info', 'auto_info' + 'accounts', 'gathered_info', 'spec_info', + 'auto_config', ]) return names + def get_category(self): + request = self.context.get('request') + if request.query_params.get('category'): + category = request.query_params.get('category') + else: + instance = self.get_instance() + category = instance.category + return category + + def get_gathered_info_serializer(self): + category = self.get_category() + from .info.gathered import category_gathered_serializer_map + serializer_cls = category_gathered_serializer_map.get(category, serializers.DictField) + return serializer_cls() + + def get_spec_info_serializer(self): + category = self.get_category() + from .info.spec import category_spec_serializer_map + serializer_cls = category_spec_serializer_map.get(category, serializers.DictField) + return serializer_cls() + class AssetDetailSerializer(DetailMixin, AssetSerializer): pass diff --git a/apps/assets/serializers/asset/custom.py b/apps/assets/serializers/asset/custom.py new file mode 100644 index 000000000..d88024218 --- /dev/null +++ b/apps/assets/serializers/asset/custom.py @@ -0,0 +1,9 @@ +from assets.models import Custom +from .common import AssetSerializer + +__all__ = ['CustomSerializer'] + + +class CustomSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Custom diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py index 9f5e97dba..da6c9574a 100644 --- a/apps/assets/serializers/asset/database.py +++ b/apps/assets/serializers/asset/database.py @@ -1,9 +1,9 @@ -from rest_framework.serializers import ValidationError from django.utils.translation import ugettext_lazy as _ +from rest_framework.serializers import ValidationError from assets.models import Database +from assets.serializers.gateway import GatewayWithAccountSecretSerializer from .common import AssetSerializer -from ..gateway import GatewayWithAccountSecretSerializer __all__ = ['DatabaseSerializer', 'DatabaseWithGatewaySerializer'] diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py index 0f6bd8f5d..c3575f43b 100644 --- a/apps/assets/serializers/asset/host.py +++ b/apps/assets/serializers/asset/host.py @@ -1,34 +1,18 @@ from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers from assets.models import Host from .common import AssetSerializer +from .info.gathered import HostGatheredInfoSerializer -__all__ = ['HostInfoSerializer', 'HostSerializer'] - - -class HostInfoSerializer(serializers.Serializer): - vendor = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Vendor')) - model = serializers.CharField(max_length=54, required=False, allow_blank=True, label=_('Model')) - sn = serializers.CharField(max_length=128, required=False, allow_blank=True, label=_('Serial number')) - cpu_model = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('CPU model')) - cpu_count = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU count')) - cpu_cores = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU cores')) - cpu_vcpus = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU vcpus')) - memory = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('Memory')) - disk_total = serializers.CharField(max_length=1024, allow_blank=True, required=False, label=_('Disk total')) - - distribution = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('OS')) - distribution_version = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS version')) - arch = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS arch')) +__all__ = ['HostSerializer'] class HostSerializer(AssetSerializer): - info = HostInfoSerializer(required=False, label=_('Info')) + gathered_info = HostGatheredInfoSerializer(required=False, read_only=True, label=_("Gathered info")) class Meta(AssetSerializer.Meta): model = Host - fields = AssetSerializer.Meta.fields + ['info'] + fields = AssetSerializer.Meta.fields + ['gathered_info'] extra_kwargs = { **AssetSerializer.Meta.extra_kwargs, 'address': { diff --git a/apps/assets/serializers/asset/info/gathered.py b/apps/assets/serializers/asset/info/gathered.py new file mode 100644 index 000000000..db0f22e65 --- /dev/null +++ b/apps/assets/serializers/asset/info/gathered.py @@ -0,0 +1,23 @@ +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + + +class HostGatheredInfoSerializer(serializers.Serializer): + vendor = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Vendor')) + model = serializers.CharField(max_length=54, required=False, allow_blank=True, label=_('Model')) + sn = serializers.CharField(max_length=128, required=False, allow_blank=True, label=_('Serial number')) + cpu_model = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('CPU model')) + cpu_count = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU count')) + cpu_cores = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU cores')) + cpu_vcpus = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU vcpus')) + memory = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('Memory')) + disk_total = serializers.CharField(max_length=1024, allow_blank=True, required=False, label=_('Disk total')) + + distribution = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('OS')) + distribution_version = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS version')) + arch = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS arch')) + + +category_gathered_serializer_map = { + 'host': HostGatheredInfoSerializer, +} diff --git a/apps/assets/serializers/asset/info/spec.py b/apps/assets/serializers/asset/info/spec.py new file mode 100644 index 000000000..5d3de54c3 --- /dev/null +++ b/apps/assets/serializers/asset/info/spec.py @@ -0,0 +1,24 @@ +from rest_framework import serializers + +from assets.models import Database, Web + + +class DatabaseSpecSerializer(serializers.ModelSerializer): + class Meta: + model = Database + fields = ['db_name', 'use_ssl', 'allow_invalid_cert'] + + +class WebSpecSerializer(serializers.ModelSerializer): + class Meta: + model = Web + fields = [ + 'autofill', 'username_selector', 'password_selector', + 'submit_selector', 'script' + ] + + +category_spec_serializer_map = { + 'database': DatabaseSpecSerializer, + 'web': WebSpecSerializer, +} diff --git a/apps/assets/serializers/gateway.py b/apps/assets/serializers/gateway.py index 78d8afda5..259bc13f8 100644 --- a/apps/assets/serializers/gateway.py +++ b/apps/assets/serializers/gateway.py @@ -3,8 +3,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from .asset import HostSerializer from .asset.common import AccountSecretSerializer +from .asset.host import HostSerializer from ..models import Gateway, Asset __all__ = ['GatewaySerializer', 'GatewayWithAccountSecretSerializer'] diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 300b3dd65..ba9def8b9 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -4,6 +4,7 @@ from rest_framework import serializers from assets.const.web import FillType from common.serializers import WritableNestedModelSerializer from common.serializers.fields import LabeledChoiceField +from common.utils import lazyproperty from ..const import Category, AllTypes from ..models import Platform, PlatformProtocol, PlatformAutomation @@ -37,7 +38,6 @@ class ProtocolSettingSerializer(serializers.Serializer): default="", allow_blank=True, label=_("Submit selector") ) script = serializers.JSONField(default=list, label=_("Script")) - # Redis auth_username = serializers.BooleanField(default=False, label=_("Auth with username")) @@ -87,6 +87,21 @@ class PlatformProtocolSerializer(serializers.ModelSerializer): ] +class PlatformCustomField(serializers.Serializer): + TYPE_CHOICES = [ + ("str", "str"), + ("int", "int"), + ("bool", "bool"), + ("choice", "choice"), + ] + name = serializers.CharField(label=_("Name"), max_length=128) + label = serializers.CharField(label=_("Label"), max_length=128) + type = serializers.ChoiceField(choices=TYPE_CHOICES, label=_("Type"), default='str') + default = serializers.CharField(default="", allow_blank=True, label=_("Default"), max_length=1024) + help_text = serializers.CharField(default="", allow_blank=True, label=_("Help text"), max_length=1024) + choices = serializers.ListField(default=list, label=_("Choices"), required=False) + + class PlatformSerializer(WritableNestedModelSerializer): SU_METHOD_CHOICES = [ ("sudo", "sudo su -"), @@ -95,19 +110,16 @@ class PlatformSerializer(WritableNestedModelSerializer): ("super", "super 15"), ("super_level", "super level 15") ] - - charset = LabeledChoiceField( - choices=Platform.CharsetChoices.choices, label=_("Charset") - ) + charset = LabeledChoiceField(choices=Platform.CharsetChoices.choices, label=_("Charset"), default='utf-8') type = LabeledChoiceField(choices=AllTypes.choices(), label=_("Type")) category = LabeledChoiceField(choices=Category.choices, label=_("Category")) - protocols = PlatformProtocolSerializer( - label=_("Protocols"), many=True, required=False + protocols = PlatformProtocolSerializer(label=_("Protocols"), many=True, required=False) + automation = PlatformAutomationSerializer(label=_("Automation"), required=False, default=dict) + su_method = LabeledChoiceField( + choices=SU_METHOD_CHOICES, label=_("Su method"), + required=False, default="sudo", allow_null=True ) - automation = PlatformAutomationSerializer(label=_("Automation"), required=False) - su_method = LabeledChoiceField(choices=SU_METHOD_CHOICES, - label=_("Su method"), required=False, default="sudo", allow_null=True - ) + custom_fields = PlatformCustomField(label=_("Custom fields"), many=True, required=False) class Meta: model = Platform @@ -115,19 +127,54 @@ class PlatformSerializer(WritableNestedModelSerializer): fields_small = fields_mini + [ "category", "type", "charset", ] - fields_other = [ - 'date_created', 'date_updated', 'created_by', 'updated_by', + read_only_fields = [ + 'internal', 'date_created', 'date_updated', + 'created_by', 'updated_by' ] fields = fields_small + [ "protocols", "domain_enabled", "su_enabled", - "su_method", "automation", "comment", - ] + fields_other + "su_method", "automation", "comment", "custom_fields", + ] + read_only_fields extra_kwargs = { "su_enabled": {"label": _('Su enabled')}, "domain_enabled": {"label": _('Domain enabled')}, "domain_default": {"label": _('Default Domain')}, } + @property + def platform_category_type(self): + if self.instance: + return self.instance.category, self.instance.type + if self.initial_data: + return self.initial_data.get('category'), self.initial_data.get('type') + raise serializers.ValidationError({'type': _("type is required")}) + + def add_type_choices(self, name, label): + tp = self.fields['type'] + tp.choices[name] = label + tp.choice_mapper[name] = label + tp.choice_strings_to_values[name] = label + + @lazyproperty + def constraints(self): + category, tp = self.platform_category_type + 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.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( diff --git a/apps/assets/signal_handlers/asset.py b/apps/assets/signal_handlers/asset.py index 107be4422..9598bec5d 100644 --- a/apps/assets/signal_handlers/asset.py +++ b/apps/assets/signal_handlers/asset.py @@ -66,11 +66,11 @@ def on_asset_create(sender, instance=None, created=False, **kwargs): ensure_asset_has_node(assets=(instance,)) # 获取资产硬件信息 - auto_info = instance.auto_info - if auto_info.get('ping_enabled'): + auto_config = instance.auto_config + if auto_config.get('ping_enabled'): logger.debug('Asset {} ping enabled, test connectivity'.format(instance.name)) test_assets_connectivity_handler(assets=(instance,)) - if auto_info.get('gather_facts_enabled'): + if auto_config.get('gather_facts_enabled'): logger.debug('Asset {} gather facts enabled, gather facts'.format(instance.name)) gather_assets_facts_handler(assets=(instance,)) diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index a9a385eb9..174dccef1 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -14,6 +14,7 @@ router.register(r'devices', api.DeviceViewSet, 'device') router.register(r'databases', api.DatabaseViewSet, 'database') router.register(r'webs', api.WebViewSet, 'web') router.register(r'clouds', api.CloudViewSet, 'cloud') +router.register(r'customs', api.CustomViewSet, 'custom') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') router.register(r'labels', api.LabelViewSet, 'label') router.register(r'nodes', api.NodeViewSet, 'node') diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index 003d8239e..f5f5bc62a 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -5,7 +5,8 @@ from accounts.const import SecretType from accounts.models import Account from acls.models import CommandGroup, CommandFilterACL from assets.models import Asset, Platform, Gateway, Domain -from assets.serializers import PlatformSerializer, AssetProtocolsSerializer +from assets.serializers.asset import AssetProtocolsSerializer +from assets.serializers.platform import PlatformSerializer from common.serializers.fields import LabeledChoiceField from common.serializers.fields import ObjectRelatedField from orgs.mixins.serializers import OrgResourceModelSerializerMixin @@ -30,14 +31,12 @@ class _ConnectionTokenAssetSerializer(serializers.ModelSerializer): class Meta: model = Asset fields = [ - 'id', 'name', 'address', 'protocols', - 'category', 'type', 'org_id', 'spec_info', - 'secret_info', + 'id', 'name', 'address', 'protocols', 'category', + 'type', 'org_id', 'spec_info', 'secret_info', ] class _SimpleAccountSerializer(serializers.ModelSerializer): - """ Account """ secret_type = LabeledChoiceField(choices=SecretType.choices, required=False, label=_('Secret type')) class Meta: @@ -46,20 +45,18 @@ class _SimpleAccountSerializer(serializers.ModelSerializer): class _ConnectionTokenAccountSerializer(serializers.ModelSerializer): - """ Account """ su_from = _SimpleAccountSerializer(required=False, label=_('Su from')) secret_type = LabeledChoiceField(choices=SecretType.choices, required=False, label=_('Secret type')) class Meta: model = Account fields = [ - 'id', 'name', 'username', 'secret_type', 'secret', 'su_from', 'privileged' + 'id', 'name', 'username', 'secret_type', + 'secret', 'su_from', 'privileged' ] class _ConnectionTokenGatewaySerializer(serializers.ModelSerializer): - """ Gateway """ - account = _SimpleAccountSerializer( required=False, source='select_account', read_only=True ) @@ -85,7 +82,8 @@ class _ConnectionTokenCommandFilterACLSerializer(serializers.ModelSerializer): class Meta: model = CommandFilterACL fields = [ - 'id', 'name', 'command_groups', 'action', 'reviewers', 'priority', 'is_active' + 'id', 'name', 'command_groups', 'action', + 'reviewers', 'priority', 'is_active' ] @@ -136,8 +134,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): 'id', 'value', 'user', 'asset', 'account', 'platform', 'command_filter_acls', 'protocol', 'domain', 'gateway', 'actions', 'expire_at', - 'from_ticket', - 'expire_now', 'connect_method', + 'from_ticket', 'expire_now', 'connect_method', ] extra_kwargs = { 'value': {'read_only': True}, diff --git a/apps/common/serializers/dynamic.py b/apps/common/serializers/dynamic.py new file mode 100644 index 000000000..22496dfc2 --- /dev/null +++ b/apps/common/serializers/dynamic.py @@ -0,0 +1,54 @@ +from rest_framework import serializers + +example_info = [ + {"name": "name", "label": "姓名", "required": False, "default": "老广", "type": "str"}, + {"name": "age", "label": "年龄", "required": False, "default": 18, "type": "int"}, +] + +type_field_map = { + "str": serializers.CharField, + "int": serializers.IntegerField, + "bool": serializers.BooleanField, + "text": serializers.CharField, + "choice": serializers.ChoiceField, +} + + +def set_default_if_need(data, i): + field_name = data.pop('name', 'Attr{}'.format(i + 1)) + data['name'] = field_name + + if not data.get('label'): + data['label'] = field_name + return data + + +def set_default_by_type(tp, data, field_info): + if tp == 'str': + data['max_length'] = 4096 + elif tp == 'choice': + choices = field_info.pop('choices', []) + if isinstance(choices, str): + choices = choices.split(',') + choices = [ + (c, c.title()) if not isinstance(c, (tuple, list)) else c + for c in choices + ] + data['choices'] = choices + return data + + +def create_serializer_class(serializer_name, fields_info): + serializer_fields = {} + fields_name = ['name', 'label', 'default', 'type', 'help_text'] + + for i, field_info in enumerate(fields_info): + data = {k: field_info.get(k) for k in fields_name} + field_type = data.pop('type', 'str') + data = set_default_by_type(field_type, data, field_info) + data = set_default_if_need(data, i) + field_name = data.pop('name') + field_class = type_field_map.get(field_type, serializers.CharField) + serializer_fields[field_name] = field_class(**data) + + return type(serializer_name, (serializers.Serializer,), serializer_fields) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 74bd80fbd..86fc7369d 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:15e96f9f31e92077ac828e248a30678e53b7c867757ae6348ae9805bc64874bc -size 138124 +oid sha256:975e9e264596ef5f7233fc1d2fb45281a5fe13f5a722fc2b9d5c40562ada069d +size 138303 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index e5af1f351..772fb0b5c 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-03 18:06+0800\n" +"POT-Creation-Date: 2023-04-07 13:57+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -176,12 +176,13 @@ msgstr "作成のみ" #: accounts/models/account.py:47 #: accounts/models/automations/gather_account.py:16 -#: accounts/serializers/account/account.py:110 +#: accounts/serializers/account/account.py:173 +#: accounts/serializers/account/account.py:206 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:131 #: acls/models/base.py:100 acls/serializers/base.py:56 -#: assets/models/asset/common.py:92 assets/models/asset/common.py:261 +#: assets/models/asset/common.py:92 assets/models/asset/common.py:268 #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19 #: assets/serializers/label.py:27 audits/models.py:48 #: authentication/models/connection_token.py:33 @@ -192,8 +193,8 @@ msgstr "作成のみ" msgid "Asset" msgstr "資産" -#: accounts/models/account.py:51 accounts/serializers/account/account.py:114 -#: authentication/serializers/connect_token_secret.py:50 +#: accounts/models/account.py:51 accounts/serializers/account/account.py:178 +#: authentication/serializers/connect_token_secret.py:48 msgid "Su from" msgstr "から切り替え" @@ -202,16 +203,22 @@ msgstr "から切り替え" msgid "Version" msgstr "バージョン" -#: accounts/models/account.py:55 accounts/serializers/account/account.py:111 +#: accounts/models/account.py:55 accounts/serializers/account/account.py:174 #: users/models/user.py:768 msgid "Source" msgstr "ソース" -#: accounts/models/account.py:58 +#: accounts/models/account.py:56 +#, fuzzy +#| msgid "Source" +msgid "Source ID" +msgstr "ソース" + +#: accounts/models/account.py:59 #: accounts/serializers/automations/change_secret.py:112 #: accounts/serializers/automations/change_secret.py:132 #: acls/models/base.py:102 acls/serializers/base.py:57 -#: assets/serializers/asset/common.py:135 assets/serializers/gateway.py:28 +#: assets/serializers/asset/common.py:107 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 #: terminal/backends/command/models.py:21 terminal/models/session/session.py:34 @@ -219,35 +226,35 @@ msgstr "ソース" msgid "Account" msgstr "アカウント" -#: accounts/models/account.py:64 +#: accounts/models/account.py:65 msgid "Can view asset account secret" msgstr "資産アカウントの秘密を表示できます" -#: accounts/models/account.py:65 +#: accounts/models/account.py:66 msgid "Can view asset history account" msgstr "資産履歴アカウントを表示できます" -#: accounts/models/account.py:66 +#: accounts/models/account.py:67 msgid "Can view asset history account secret" msgstr "資産履歴アカウントパスワードを表示できます" -#: accounts/models/account.py:67 +#: accounts/models/account.py:68 msgid "Can verify account" msgstr "アカウントを確認できます" -#: accounts/models/account.py:68 +#: accounts/models/account.py:69 msgid "Can push account" msgstr "アカウントをプッシュできます" -#: accounts/models/account.py:109 +#: accounts/models/account.py:110 msgid "Account template" msgstr "アカウント テンプレート" -#: accounts/models/account.py:114 +#: accounts/models/account.py:115 msgid "Can view asset account template secret" msgstr "アセット アカウント テンプレートのパスワードを表示できます" -#: accounts/models/account.py:115 +#: accounts/models/account.py:116 msgid "Can change asset account template secret" msgstr "アセット アカウント テンプレートのパスワードを変更できます" @@ -344,12 +351,11 @@ msgid "Can add push account execution" msgstr "プッシュ アカウントの作成の実行" #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 -#: accounts/serializers/account/account.py:175 +#: accounts/serializers/account/account.py:383 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 -#: assets/serializers/asset/common.py:77 -#: authentication/serializers/connect_token_secret.py:41 -#: authentication/serializers/connect_token_secret.py:51 +#: authentication/serializers/connect_token_secret.py:40 +#: authentication/serializers/connect_token_secret.py:49 msgid "Secret type" msgstr "鍵の種類" @@ -394,7 +400,8 @@ msgstr "開始日" msgid "Date finished" msgstr "終了日" -#: accounts/models/automations/change_secret.py:94 assets/const/automation.py:8 +#: accounts/models/automations/change_secret.py:94 +#: accounts/serializers/account/account.py:208 assets/const/automation.py:8 #: common/const/choices.py:20 msgid "Error" msgstr "間違い" @@ -450,7 +457,7 @@ msgstr "トリガー方式" #: accounts/models/automations/push_account.py:16 acls/models/base.py:81 #: acls/serializers/base.py:81 acls/serializers/login_acl.py:25 #: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82 -#: authentication/serializers/connect_token_secret.py:109 +#: authentication/serializers/connect_token_secret.py:107 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "アクション" @@ -469,10 +476,10 @@ msgstr "アカウントの確認" #: assets/models/asset/common.py:90 assets/models/asset/common.py:102 #: 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:64 -#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:155 -#: assets/serializers/platform.py:148 -#: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 +#: assets/models/platform.py:13 assets/models/platform.py:65 +#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:93 +#: assets/serializers/platform.py:184 +#: authentication/serializers/connect_token_secret.py:101 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 #: orgs/models.py:69 perms/models/asset_permission.py:56 rbac/models/role.py:29 @@ -480,7 +487,7 @@ msgstr "アカウントの確認" #: terminal/models/applet/applet.py:27 terminal/models/component/endpoint.py:12 #: 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 +#: terminal/models/component/terminal.py:85 users/forms/profile.py:33 #: users/models/group.py:13 users/models/user.py:717 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -493,7 +500,7 @@ msgstr "特権アカウント" #: accounts/models/base.py:40 assets/models/asset/common.py:109 #: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39 #: assets/models/label.py:22 -#: authentication/serializers/connect_token_secret.py:107 +#: authentication/serializers/connect_token_secret.py:105 #: terminal/models/applet/applet.py:32 users/serializers/user.py:162 msgid "Is active" msgstr "アクティブです。" @@ -539,38 +546,38 @@ msgstr "" "{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" "情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" -#: accounts/serializers/account/account.py:80 -#: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75 +#: accounts/serializers/account/account.py:26 +#: settings/serializers/auth/sms.py:75 msgid "Template" msgstr "テンプレート" -#: accounts/serializers/account/account.py:83 -#: assets/serializers/asset/common.py:69 +#: accounts/serializers/account/account.py:29 msgid "Push now" msgstr "今すぐプッシュ" -#: accounts/serializers/account/account.py:85 -#: accounts/serializers/account/base.py:64 -msgid "Has secret" -msgstr "エスクローされたパスワード" +#: accounts/serializers/account/account.py:33 +#, fuzzy +#| msgid "Runas policy" +msgid "Exist policy" +msgstr "ユーザー ポリシー" -#: accounts/serializers/account/account.py:90 applications/models.py:11 -#: assets/models/label.py:21 assets/models/platform.py:65 -#: assets/serializers/asset/common.py:131 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:100 assets/serializers/platform.py:149 +#: accounts/serializers/account/account.py:153 applications/models.py:11 +#: assets/models/label.py:21 assets/models/platform.py:66 +#: assets/serializers/asset/common.py:103 assets/serializers/cagegory.py:8 +#: assets/serializers/platform.py:111 assets/serializers/platform.py:185 #: 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:91 +#: accounts/serializers/account/account.py:154 #: 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:66 -#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:99 -#: audits/serializers.py:48 -#: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 +#: assets/models/cmd_filter.py:74 assets/models/platform.py:67 +#: assets/serializers/asset/common.py:104 assets/serializers/platform.py:95 +#: assets/serializers/platform.py:110 audits/serializers.py:48 +#: authentication/serializers/connect_token_secret.py:114 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29 @@ -582,15 +589,56 @@ msgstr "カテゴリ" msgid "Type" msgstr "タイプ" -#: accounts/serializers/account/account.py:106 +#: accounts/serializers/account/account.py:169 msgid "Asset not found" msgstr "資産が存在しません" -#: accounts/serializers/account/account.py:118 ops/models/base.py:19 -msgid "Account policy" -msgstr "アカウント ポリシー" +#: accounts/serializers/account/account.py:175 +#: accounts/serializers/account/base.py:64 +msgid "Has secret" +msgstr "エスクローされたパスワード" -#: accounts/serializers/account/account.py:185 acls/models/base.py:98 +#: accounts/serializers/account/account.py:207 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:209 +#, fuzzy +#| msgid "Change by" +msgid "Changed" +msgstr "による変更" + +#: accounts/serializers/account/account.py:213 +#: accounts/serializers/automations/base.py:22 +#: assets/models/automations/base.py:19 +#: assets/serializers/automations/base.py:20 ops/models/base.py:17 +#: ops/models/job.py:104 ops/serializers/job.py:20 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 +msgid "Assets" +msgstr "資産" + +#: accounts/serializers/account/account.py:284 +#, fuzzy +#| msgid "Name already exists" +msgid "Account already exists" +msgstr "名前は既に存在します。" + +#: accounts/serializers/account/account.py:320 +#, fuzzy, python-format +#| msgid "Current user not support mfa type: {}" +msgid "Asset does not support this secret type: %s" +msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" + +#: accounts/serializers/account/account.py:351 +#, fuzzy +#| msgid "Account name" +msgid "Account has exist" +msgstr "アカウント名" + +#: accounts/serializers/account/account.py:393 acls/models/base.py:98 #: acls/models/login_acl.py:13 acls/serializers/base.py:55 #: 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 @@ -608,7 +656,7 @@ msgstr "アカウント ポリシー" msgid "User" msgstr "ユーザー" -#: accounts/serializers/account/account.py:186 +#: accounts/serializers/account/account.py:394 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:98 terminal/notifications.py:146 msgid "Date" @@ -639,19 +687,11 @@ msgstr "資産タイプ" msgid "Key password" msgstr "キーパスワード" -#: accounts/serializers/account/base.py:81 -#: assets/serializers/asset/common.py:307 +#: accounts/serializers/account/base.py:80 +#: assets/serializers/asset/common.py:298 msgid "Spec info" msgstr "特別情報" -#: accounts/serializers/automations/base.py:22 -#: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 ops/models/base.py:17 -#: ops/models/job.py:104 ops/serializers/job.py:20 -#: terminal/templates/terminal/_msg_command_execute_alert.html:16 -msgid "Assets" -msgstr "資産" - #: accounts/serializers/automations/base.py:23 #: assets/models/asset/common.py:108 assets/models/automations/base.py:18 #: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21 @@ -734,11 +774,6 @@ msgstr "パスワードには `\"` を含まない" msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" -#: accounts/validator.py:41 -#, python-brace-format -msgid "{field_name} not a legal option" -msgstr "" - #: acls/apps.py:7 msgid "Acls" msgstr "Acls" @@ -768,7 +803,7 @@ msgstr "1-100、低い値は最初に一致します" #: acls/models/base.py:82 acls/serializers/base.py:75 #: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86 -#: authentication/serializers/connect_token_secret.py:82 +#: authentication/serializers/connect_token_secret.py:79 msgid "Reviewers" msgstr "レビュー担当者" @@ -793,7 +828,7 @@ msgid "Regex" msgstr "正規情報" #: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:30 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 msgid "Content" msgstr "コンテンツ" @@ -807,7 +842,7 @@ msgstr "家を無視する" #: acls/models/command_acl.py:33 acls/models/command_acl.py:96 #: acls/serializers/command_acl.py:28 -#: authentication/serializers/connect_token_secret.py:79 +#: authentication/serializers/connect_token_secret.py:76 msgid "Command group" msgstr "コマンドグループ" @@ -1013,31 +1048,37 @@ msgstr "テストゲートウェイ" msgid "Gather facts" msgstr "資産情報の収集" -#: assets/const/category.py:11 assets/models/asset/host.py:8 +#: assets/const/category.py:10 assets/models/asset/host.py:8 #: settings/serializers/auth/radius.py:16 settings/serializers/auth/sms.py:67 #: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "ホスト" -#: assets/const/category.py:12 assets/models/asset/device.py:8 +#: assets/const/category.py:11 assets/models/asset/device.py:8 msgid "Device" msgstr "インターネット機器" -#: assets/const/category.py:13 assets/models/asset/database.py:9 -#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:119 +#: assets/const/category.py:12 assets/models/asset/database.py:9 +#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:91 msgid "Database" msgstr "データベース" -#: assets/const/category.py:14 +#: assets/const/category.py:13 msgid "Cloud service" msgstr "クラウド サービス" -#: assets/const/category.py:15 assets/models/asset/web.py:16 audits/const.py:33 +#: assets/const/category.py:14 assets/models/asset/web.py:16 audits/const.py:33 #: terminal/models/applet/applet.py:25 msgid "Web" msgstr "Web" +#: assets/const/category.py:15 +#, fuzzy +#| msgid "Custom user" +msgid "Custom type" +msgstr "カスタムユーザー" + #: assets/const/cloud.py:7 msgid "Public cloud" msgstr "パブリック クラウド" @@ -1071,7 +1112,7 @@ msgstr "ファイアウォール" msgid "Other" msgstr "その他" -#: assets/const/types.py:200 +#: assets/const/types.py:214 msgid "All types" msgstr "いろんなタイプ" @@ -1089,7 +1130,7 @@ msgid "Basic" msgstr "基本" #: assets/const/web.py:61 assets/models/asset/web.py:13 -#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:39 +#: assets/serializers/asset/common.py:99 assets/serializers/platform.py:40 msgid "Script" msgstr "脚本" @@ -1109,7 +1150,7 @@ 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:110 #: 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:158 +#: terminal/models/applet/applet.py:36 terminal/models/applet/applet.py:160 #: terminal/models/applet/host.py:111 terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:100 #: terminal/models/session/session.py:47 tickets/models/comment.py:32 @@ -1158,7 +1199,7 @@ msgid "Username same with user" msgstr "ユーザーと同じユーザー名" #: assets/models/_user.py:52 authentication/models/connection_token.py:38 -#: authentication/serializers/connect_token_secret.py:104 +#: authentication/serializers/connect_token_secret.py:102 #: terminal/models/applet/applet.py:34 terminal/serializers/session.py:20 #: terminal/serializers/session.py:41 terminal/serializers/storage.py:68 msgid "Protocol" @@ -1218,19 +1259,19 @@ msgstr "クラウド サービス" msgid "Port" msgstr "ポート" -#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:156 +#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:129 msgid "Address" msgstr "アドレス" -#: assets/models/asset/common.py:104 assets/models/platform.py:95 -#: authentication/serializers/connect_token_secret.py:108 +#: assets/models/asset/common.py:104 assets/models/platform.py:100 +#: authentication/serializers/connect_token_secret.py:106 #: perms/serializers/user_permission.py:24 -#: xpack/plugins/cloud/serializers/account_attrs.py:197 +#: xpack/plugins/cloud/serializers/account_attrs.py:187 msgid "Platform" msgstr "プラットフォーム" #: assets/models/asset/common.py:106 assets/models/domain.py:21 -#: authentication/serializers/connect_token_secret.py:126 +#: authentication/serializers/connect_token_secret.py:124 #: perms/serializers/user_permission.py:28 msgid "Domain" msgstr "ドメイン" @@ -1239,27 +1280,42 @@ msgstr "ドメイン" msgid "Labels" msgstr "ラベル" -#: assets/models/asset/common.py:111 assets/serializers/asset/host.py:27 -msgid "Info" -msgstr "情報" +#: assets/models/asset/common.py:111 +#, fuzzy +#| msgid "Gather asset hardware info" +msgid "Gathered info" +msgstr "資産ハードウェア情報の収集" -#: assets/models/asset/common.py:264 +#: assets/models/asset/common.py:112 assets/serializers/asset/common.py:109 +#: assets/serializers/asset/common.py:179 +#, fuzzy +#| msgid "Auto info" +msgid "Custom info" +msgstr "自動情報" + +#: assets/models/asset/common.py:271 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" -#: assets/models/asset/common.py:265 +#: assets/models/asset/common.py:272 msgid "Can test asset connectivity" msgstr "資産接続をテストできます" -#: assets/models/asset/common.py:266 +#: assets/models/asset/common.py:273 msgid "Can match asset" msgstr "アセットを一致させることができます" -#: assets/models/asset/common.py:267 +#: assets/models/asset/common.py:274 msgid "Can change asset nodes" msgstr "資産ノードを変更できます" -#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:120 +#: assets/models/asset/custom.py:8 +#, fuzzy +#| msgid "Custom user" +msgid "Custom asset" +msgstr "カスタムユーザー" + +#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:92 #: settings/serializers/email.py:37 msgid "Use SSL" msgstr "SSLの使用" @@ -1276,31 +1332,31 @@ msgstr "クライアント証明書" msgid "Client key" msgstr "クライアントキー" -#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:121 +#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:93 msgid "Allow invalid cert" msgstr "証明書チェックを無視" -#: assets/models/asset/web.py:9 assets/serializers/platform.py:29 +#: assets/models/asset/web.py:9 assets/serializers/platform.py:30 msgid "Autofill" msgstr "自動充填" -#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:124 -#: assets/serializers/platform.py:31 +#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:96 +#: assets/serializers/platform.py:32 msgid "Username selector" msgstr "ユーザー名ピッカー" -#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:125 -#: assets/serializers/platform.py:34 +#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:97 +#: assets/serializers/platform.py:35 msgid "Password selector" msgstr "パスワードセレクター" -#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:126 -#: assets/serializers/platform.py:37 +#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:98 +#: assets/serializers/platform.py:38 msgid "Submit selector" msgstr "ボタンセレクターを確認する" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:306 rbac/tree.py:35 +#: assets/serializers/asset/common.py:297 rbac/tree.py:35 msgid "Accounts" msgstr "アカウント" @@ -1314,7 +1370,7 @@ msgstr "アセットの自動化タスク" #: assets/models/automations/base.py:112 audits/models.py:177 #: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:183 -#: terminal/models/applet/applet.py:157 terminal/models/applet/host.py:108 +#: terminal/models/applet/applet.py:159 terminal/models/applet/host.py:108 #: 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,6 +1438,7 @@ msgid "Asset group" msgstr "資産グループ" #: assets/models/group.py:34 assets/models/platform.py:17 +#: assets/serializers/platform.py:97 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" msgstr "デフォルト" @@ -1397,14 +1454,15 @@ msgstr "システム" #: assets/models/label.py:19 assets/models/node.py:544 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 #: authentication/models/connection_token.py:26 -#: authentication/serializers/connect_token_secret.py:115 +#: authentication/serializers/connect_token_secret.py:113 #: common/serializers/common.py:80 settings/models.py:34 msgid "Value" msgstr "値" -#: assets/models/label.py:40 assets/serializers/asset/common.py:133 +#: assets/models/label.py:40 assets/serializers/asset/common.py:105 #: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13 -#: authentication/serializers/connect_token_secret.py:114 +#: assets/serializers/platform.py:94 +#: authentication/serializers/connect_token_secret.py:112 #: common/serializers/common.py:79 settings/serializers/sms.py:7 msgid "Label" msgstr "ラベル" @@ -1446,90 +1504,102 @@ msgstr "主要" msgid "Required" msgstr "必要" -#: assets/models/platform.py:18 settings/serializers/settings.py:65 +#: assets/models/platform.py:18 +#, fuzzy +#| msgid "Public IP" +msgid "Public" +msgstr "パブリックIP" + +#: assets/models/platform.py:19 settings/serializers/settings.py:65 #: users/templates/users/reset_password.html:29 msgid "Setting" msgstr "設定" -#: assets/models/platform.py:30 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:31 +#: assets/models/platform.py:32 msgid "Ansible config" msgstr "Ansible 構成" -#: assets/models/platform.py:32 assets/serializers/platform.py:60 +#: assets/models/platform.py:33 assets/serializers/platform.py:60 msgid "Ping enabled" msgstr "アセット ディスカバリを有効にする" -#: assets/models/platform.py:33 assets/serializers/platform.py:61 +#: assets/models/platform.py:34 assets/serializers/platform.py:61 msgid "Ping method" msgstr "資産検出方法" -#: assets/models/platform.py:34 assets/models/platform.py:47 +#: assets/models/platform.py:35 assets/models/platform.py:48 #: assets/serializers/platform.py:62 msgid "Gather facts enabled" msgstr "資産情報の収集を有効にする" -#: assets/models/platform.py:35 assets/models/platform.py:49 +#: assets/models/platform.py:36 assets/models/platform.py:50 #: assets/serializers/platform.py:63 msgid "Gather facts method" msgstr "情報収集の方法" -#: assets/models/platform.py:36 assets/serializers/platform.py:66 +#: assets/models/platform.py:37 assets/serializers/platform.py:66 msgid "Change secret enabled" msgstr "パスワードの変更が有効" -#: assets/models/platform.py:38 assets/serializers/platform.py:67 +#: assets/models/platform.py:39 assets/serializers/platform.py:67 msgid "Change secret method" msgstr "パスワード変更モード" -#: assets/models/platform.py:40 assets/serializers/platform.py:68 +#: assets/models/platform.py:41 assets/serializers/platform.py:68 msgid "Push account enabled" msgstr "アカウントのプッシュを有効にする" -#: assets/models/platform.py:42 assets/serializers/platform.py:69 +#: assets/models/platform.py:43 assets/serializers/platform.py:69 msgid "Push account method" msgstr "アカウントプッシュ方式" -#: assets/models/platform.py:44 assets/serializers/platform.py:64 +#: assets/models/platform.py:45 assets/serializers/platform.py:64 msgid "Verify account enabled" msgstr "アカウントの確認をオンにする" -#: assets/models/platform.py:46 assets/serializers/platform.py:65 +#: assets/models/platform.py:47 assets/serializers/platform.py:65 msgid "Verify account method" msgstr "アカウント認証方法" -#: assets/models/platform.py:67 tickets/models/ticket/general.py:300 +#: assets/models/platform.py:68 tickets/models/ticket/general.py:300 msgid "Meta" msgstr "メタ" -#: assets/models/platform.py:68 +#: assets/models/platform.py:69 msgid "Internal" msgstr "ビルトイン" -#: assets/models/platform.py:71 assets/serializers/platform.py:97 +#: assets/models/platform.py:73 assets/serializers/platform.py:109 msgid "Charset" msgstr "シャーセット" -#: assets/models/platform.py:73 assets/serializers/platform.py:124 +#: assets/models/platform.py:75 assets/serializers/platform.py:133 msgid "Domain enabled" msgstr "ドメインを有効にする" -#: assets/models/platform.py:75 assets/serializers/platform.py:123 +#: assets/models/platform.py:77 assets/serializers/platform.py:132 msgid "Su enabled" msgstr "アカウントの切り替えを有効にする" -#: assets/models/platform.py:76 assets/serializers/platform.py:106 +#: assets/models/platform.py:78 assets/serializers/platform.py:115 msgid "Su method" msgstr "アカウントの切り替え方法" -#: assets/models/platform.py:78 assets/serializers/platform.py:104 +#: assets/models/platform.py:81 assets/serializers/platform.py:113 msgid "Automation" msgstr "オートメーション" +#: assets/models/platform.py:83 assets/serializers/platform.py:118 +#, fuzzy +#| msgid "Custom user" +msgid "Custom fields" +msgstr "カスタムユーザー" + #: assets/models/utils.py:18 #, python-format msgid "%(value)s is not an even number" @@ -1543,36 +1613,36 @@ msgstr "" "プラットフォームタイプがスキップされた資産に合致しない、資産内の一括更新プ" "ラットフォーム" -#: assets/serializers/asset/common.py:123 +#: assets/serializers/asset/common.py:95 msgid "Auto fill" msgstr "自動充填" -#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:102 -#: authentication/serializers/connect_token_secret.py:28 -#: authentication/serializers/connect_token_secret.py:66 +#: assets/serializers/asset/common.py:106 assets/serializers/platform.py:112 +#: authentication/serializers/connect_token_secret.py:29 +#: authentication/serializers/connect_token_secret.py:63 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 msgid "Protocols" msgstr "プロトコル" -#: assets/serializers/asset/common.py:136 -#: assets/serializers/asset/common.py:157 +#: assets/serializers/asset/common.py:108 +#: assets/serializers/asset/common.py:130 msgid "Node path" msgstr "ノードパスです" -#: assets/serializers/asset/common.py:154 -#: assets/serializers/asset/common.py:308 +#: assets/serializers/asset/common.py:127 +#: assets/serializers/asset/common.py:299 msgid "Auto info" msgstr "自動情報" -#: assets/serializers/asset/common.py:232 +#: assets/serializers/asset/common.py:220 msgid "Platform not exist" msgstr "プラットフォームが存在しません" -#: assets/serializers/asset/common.py:267 +#: assets/serializers/asset/common.py:255 msgid "port out of range (1-65535)" msgstr "ポート番号が範囲外です (1-65535)" -#: assets/serializers/asset/common.py:274 +#: assets/serializers/asset/common.py:262 msgid "Protocol is required: {}" msgstr "プロトコルが必要です: {}" @@ -1621,7 +1691,7 @@ msgid "Disk total" msgstr "ディスクの合計" #: assets/serializers/asset/host.py:21 -#: authentication/serializers/connect_token_secret.py:105 +#: authentication/serializers/connect_token_secret.py:103 msgid "OS" msgstr "OS" @@ -1633,6 +1703,10 @@ msgstr "システムバージョン" msgid "OS arch" msgstr "システムアーキテクチャ" +#: assets/serializers/asset/host.py:27 +msgid "Info" +msgstr "情報" + #: assets/serializers/cagegory.py:9 msgid "Constraints" msgstr "制約" @@ -1661,11 +1735,11 @@ msgstr "含まれない:/" msgid "The same level node name cannot be the same" msgstr "同じレベルのノード名を同じにすることはできません。" -#: assets/serializers/platform.py:25 +#: assets/serializers/platform.py:26 msgid "SFTP enabled" msgstr "SFTP が有効" -#: assets/serializers/platform.py:26 +#: assets/serializers/platform.py:27 msgid "SFTP home" msgstr "SFTP ルート パス" @@ -1681,11 +1755,17 @@ msgstr "アカウント収集を有効にする" msgid "Gather accounts method" msgstr "アカウントの収集方法" -#: assets/serializers/platform.py:125 +#: assets/serializers/platform.py:134 msgid "Default Domain" msgstr "デフォルト ドメイン" -#: assets/serializers/platform.py:137 +#: assets/serializers/platform.py:143 +#, fuzzy +#| msgid "test_phone is required" +msgid "type is required" +msgstr "携帯番号をテストこのフィールドは必須です" + +#: assets/serializers/platform.py:173 #, fuzzy #| msgid "Protocol is required: {}" msgid "Protocols is required" @@ -1826,7 +1906,7 @@ msgid "Change password" msgstr "パスワードを変更する" #: audits/const.py:34 settings/serializers/terminal.py:6 -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:161 +#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:163 #: terminal/serializers/session.py:48 msgid "Terminal" msgstr "ターミナル" @@ -2501,23 +2581,23 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" -#: authentication/serializers/connect_token_secret.py:106 +#: authentication/serializers/connect_token_secret.py:104 msgid "Is builtin" msgstr "ビルトイン" -#: authentication/serializers/connect_token_secret.py:110 +#: authentication/serializers/connect_token_secret.py:108 msgid "Options" msgstr "オプション" -#: authentication/serializers/connect_token_secret.py:117 +#: authentication/serializers/connect_token_secret.py:115 msgid "Component" msgstr "コンポーネント" -#: authentication/serializers/connect_token_secret.py:128 +#: authentication/serializers/connect_token_secret.py:126 msgid "Expired now" msgstr "すぐに期限切れ" -#: authentication/serializers/connect_token_secret.py:148 +#: authentication/serializers/connect_token_secret.py:145 #: authentication/templates/authentication/_access_key_modal.html:30 #: perms/models/perm_node.py:21 users/serializers/group.py:33 msgid "ID" @@ -3420,6 +3500,10 @@ msgstr "アルグ" msgid "Creator" msgstr "作成者" +#: ops/models/base.py:19 +msgid "Account policy" +msgstr "アカウント ポリシー" + #: ops/models/base.py:20 msgid "Last execution" msgstr "最後の実行" @@ -3453,13 +3537,6 @@ msgstr "タスクモニターを表示できます" msgid "Kwargs" msgstr "クワーグ" -#: 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 "状態" - #: ops/models/celery.py:61 terminal/models/session/sharing.py:114 #: tickets/const.py:25 msgid "Finished" @@ -3973,7 +4050,7 @@ msgid "My assets" msgstr "私の資産" #: rbac/tree.py:56 terminal/models/applet/applet.py:43 -#: terminal/models/applet/applet.py:154 terminal/models/applet/host.py:28 +#: terminal/models/applet/applet.py:156 terminal/models/applet/host.py:28 #: terminal/serializers/applet.py:15 msgid "Applet" msgstr "リモートアプリケーション" @@ -4473,7 +4550,7 @@ msgid "SSO auth key TTL" msgstr "Token有効期間" #: settings/serializers/auth/sso.py:17 -#: xpack/plugins/cloud/serializers/account_attrs.py:194 +#: xpack/plugins/cloud/serializers/account_attrs.py:184 msgid "Unit: second" msgstr "単位: 秒" @@ -5063,11 +5140,11 @@ msgstr "" "ヒント: Luna ページでグラフィック アセットを接続するときに使用するデフォルト" "の解像度" -#: settings/tasks/ldap.py:24 +#: settings/tasks/ldap.py:25 msgid "Import ldap user" msgstr "LDAP ユーザーのインポート" -#: settings/tasks/ldap.py:45 +#: settings/tasks/ldap.py:47 msgid "Periodic import ldap user" msgstr "LDAP ユーザーを定期的にインポートする" @@ -5258,8 +5335,8 @@ msgstr "期限切れです。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" @@ -5280,34 +5357,34 @@ msgid "" " " msgstr "" "\n" -" クリックしてください リンク パスワードの更新\n" +" クリックしてください リンク パスワードの更新\n" " " #: templates/_message.html:43 #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" -" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" +" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" " " #: templates/_message.html:56 #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" +" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -5523,7 +5600,7 @@ msgstr "ホスト" msgid "Applet pkg not valid, Missing file {}" msgstr "無効なアプレット パッケージ、ファイル {} がありません" -#: terminal/models/applet/applet.py:156 terminal/models/applet/host.py:34 +#: terminal/models/applet/applet.py:158 terminal/models/applet/host.py:34 #: terminal/models/applet/host.py:106 msgid "Hosting" msgstr "ホスト マシン" @@ -5629,28 +5706,28 @@ msgid "Default storage" msgstr "デフォルトのストレージ" #: terminal/models/component/storage.py:140 -#: terminal/models/component/terminal.py:90 +#: terminal/models/component/terminal.py:91 msgid "Command storage" msgstr "コマンドストレージ" #: terminal/models/component/storage.py:200 -#: terminal/models/component/terminal.py:91 +#: terminal/models/component/terminal.py:92 msgid "Replay storage" msgstr "再生ストレージ" -#: terminal/models/component/terminal.py:87 +#: terminal/models/component/terminal.py:88 msgid "type" msgstr "タイプ" -#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:51 +#: terminal/models/component/terminal.py:90 terminal/serializers/command.py:51 msgid "Remote Address" msgstr "リモートアドレス" -#: terminal/models/component/terminal.py:92 +#: terminal/models/component/terminal.py:93 msgid "Application User" msgstr "ユーザーの適用" -#: terminal/models/component/terminal.py:163 +#: terminal/models/component/terminal.py:165 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" @@ -7359,13 +7436,13 @@ msgstr "ファイルはJSON形式です。" msgid "IP address invalid `{}`, {}" msgstr "IPアドレスが無効: '{}', {}" -#: xpack/plugins/cloud/serializers/account_attrs.py:172 +#: xpack/plugins/cloud/serializers/account_attrs.py:162 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" msgstr "形式はコンマ区切りの文字列です,例:192.168.1.0/24,10.0.0.0-10.0.0.255" -#: xpack/plugins/cloud/serializers/account_attrs.py:176 +#: xpack/plugins/cloud/serializers/account_attrs.py:166 msgid "" "The port is used to detect the validity of the IP address. When the " "synchronization task is executed, only the valid IP address will be " @@ -7375,19 +7452,19 @@ msgstr "" "実行されると、有効な IP アドレスのみが同期されます。
ポートが0の場合、す" "べてのIPアドレスが有効です。" -#: xpack/plugins/cloud/serializers/account_attrs.py:184 +#: xpack/plugins/cloud/serializers/account_attrs.py:174 msgid "Hostname prefix" msgstr "ホスト名プレフィックス" -#: xpack/plugins/cloud/serializers/account_attrs.py:187 +#: xpack/plugins/cloud/serializers/account_attrs.py:177 msgid "IP segment" msgstr "IP セグメント" -#: xpack/plugins/cloud/serializers/account_attrs.py:191 +#: xpack/plugins/cloud/serializers/account_attrs.py:181 msgid "Test port" msgstr "テストポート" -#: xpack/plugins/cloud/serializers/account_attrs.py:194 +#: xpack/plugins/cloud/serializers/account_attrs.py:184 msgid "Test timeout" msgstr "テストタイムアウト" @@ -7469,22 +7546,27 @@ msgstr "ライセンスのインポートに成功" msgid "License is invalid" msgstr "ライセンスが無効です" -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:138 +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:135 msgid "License" msgstr "ライセンス" -#: xpack/plugins/license/models.py:80 +#: xpack/plugins/license/models.py:79 msgid "Standard edition" msgstr "標準版" -#: xpack/plugins/license/models.py:82 +#: xpack/plugins/license/models.py:81 msgid "Enterprise edition" msgstr "エンタープライズ版" -#: xpack/plugins/license/models.py:84 +#: xpack/plugins/license/models.py:83 msgid "Ultimate edition" msgstr "究極のエディション" -#: xpack/plugins/license/models.py:86 +#: xpack/plugins/license/models.py:85 msgid "Community edition" msgstr "コミュニティ版" + +#, fuzzy +#~| msgid "Custom user" +#~ msgid "Custom" +#~ msgstr "カスタムユーザー" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 7cf7ca157..3dbd987da 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:43695645a64669ba25c4fdfd413ce497a07592c320071b399cbb4f54466441e3 -size 113361 +oid sha256:035f9429613b541f229855a7d36c98e5f4736efce54dcd21119660dd6d89d94e +size 114269 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 95a5402be..29286bd92 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-03 18:06+0800\n" +"POT-Creation-Date: 2023-04-07 13:57+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -175,12 +175,13 @@ msgstr "仅创建" #: accounts/models/account.py:47 #: accounts/models/automations/gather_account.py:16 -#: accounts/serializers/account/account.py:110 +#: accounts/serializers/account/account.py:173 +#: accounts/serializers/account/account.py:206 #: accounts/serializers/account/gathered_account.py:10 #: accounts/serializers/automations/change_secret.py:111 #: accounts/serializers/automations/change_secret.py:131 #: acls/models/base.py:100 acls/serializers/base.py:56 -#: assets/models/asset/common.py:92 assets/models/asset/common.py:261 +#: assets/models/asset/common.py:92 assets/models/asset/common.py:268 #: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19 #: assets/serializers/label.py:27 audits/models.py:48 #: authentication/models/connection_token.py:33 @@ -191,8 +192,8 @@ msgstr "仅创建" msgid "Asset" msgstr "资产" -#: accounts/models/account.py:51 accounts/serializers/account/account.py:114 -#: authentication/serializers/connect_token_secret.py:50 +#: accounts/models/account.py:51 accounts/serializers/account/account.py:178 +#: authentication/serializers/connect_token_secret.py:48 msgid "Su from" msgstr "切换自" @@ -201,16 +202,20 @@ msgstr "切换自" msgid "Version" msgstr "版本" -#: accounts/models/account.py:55 accounts/serializers/account/account.py:111 +#: accounts/models/account.py:55 accounts/serializers/account/account.py:174 #: users/models/user.py:768 msgid "Source" msgstr "来源" -#: accounts/models/account.py:58 +#: accounts/models/account.py:56 +msgid "Source ID" +msgstr "来源 ID" + +#: accounts/models/account.py:59 #: accounts/serializers/automations/change_secret.py:112 #: accounts/serializers/automations/change_secret.py:132 #: acls/models/base.py:102 acls/serializers/base.py:57 -#: assets/serializers/asset/common.py:135 assets/serializers/gateway.py:28 +#: assets/serializers/asset/common.py:107 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 #: terminal/backends/command/models.py:21 terminal/models/session/session.py:34 @@ -218,35 +223,35 @@ msgstr "来源" msgid "Account" msgstr "账号" -#: accounts/models/account.py:64 +#: accounts/models/account.py:65 msgid "Can view asset account secret" msgstr "可以查看资产账号密码" -#: accounts/models/account.py:65 +#: accounts/models/account.py:66 msgid "Can view asset history account" msgstr "可以查看资产历史账号" -#: accounts/models/account.py:66 +#: accounts/models/account.py:67 msgid "Can view asset history account secret" msgstr "可以查看资产历史账号密码" -#: accounts/models/account.py:67 +#: accounts/models/account.py:68 msgid "Can verify account" msgstr "可以验证账号" -#: accounts/models/account.py:68 +#: accounts/models/account.py:69 msgid "Can push account" msgstr "可以推送账号" -#: accounts/models/account.py:109 +#: accounts/models/account.py:110 msgid "Account template" msgstr "账号模版" -#: accounts/models/account.py:114 +#: accounts/models/account.py:115 msgid "Can view asset account template secret" msgstr "可以查看资产账号模版密码" -#: accounts/models/account.py:115 +#: accounts/models/account.py:116 msgid "Can change asset account template secret" msgstr "可以更改资产账号模版密码" @@ -343,12 +348,11 @@ msgid "Can add push account execution" msgstr "创建推送账号执行" #: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 -#: accounts/serializers/account/account.py:175 +#: accounts/serializers/account/account.py:383 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 -#: assets/serializers/asset/common.py:77 -#: authentication/serializers/connect_token_secret.py:41 -#: authentication/serializers/connect_token_secret.py:51 +#: authentication/serializers/connect_token_secret.py:40 +#: authentication/serializers/connect_token_secret.py:49 msgid "Secret type" msgstr "密文类型" @@ -393,7 +397,8 @@ msgstr "开始日期" msgid "Date finished" msgstr "结束日期" -#: accounts/models/automations/change_secret.py:94 assets/const/automation.py:8 +#: accounts/models/automations/change_secret.py:94 +#: accounts/serializers/account/account.py:208 assets/const/automation.py:8 #: common/const/choices.py:20 msgid "Error" msgstr "错误" @@ -432,10 +437,8 @@ msgid "Gather account automation" msgstr "自动化收集账号" #: accounts/models/automations/gather_account.py:56 -#, fuzzy -#| msgid "Is service account" msgid "Is sync account" -msgstr "服务账号" +msgstr "是否同步账号" #: accounts/models/automations/gather_account.py:71 #: accounts/tasks/gather_accounts.py:29 @@ -449,7 +452,7 @@ msgstr "触发方式" #: accounts/models/automations/push_account.py:16 acls/models/base.py:81 #: acls/serializers/base.py:81 acls/serializers/login_acl.py:25 #: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82 -#: authentication/serializers/connect_token_secret.py:109 +#: authentication/serializers/connect_token_secret.py:107 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "动作" @@ -468,10 +471,10 @@ msgstr "账号验证" #: assets/models/asset/common.py:90 assets/models/asset/common.py:102 #: 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:64 -#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:155 -#: assets/serializers/platform.py:148 -#: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21 +#: assets/models/platform.py:13 assets/models/platform.py:65 +#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:93 +#: assets/serializers/platform.py:184 +#: authentication/serializers/connect_token_secret.py:101 ops/mixin.py:21 #: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57 #: ops/models/job.py:91 ops/models/playbook.py:23 ops/serializers/job.py:19 #: orgs/models.py:69 perms/models/asset_permission.py:56 rbac/models/role.py:29 @@ -479,7 +482,7 @@ msgstr "账号验证" #: terminal/models/applet/applet.py:27 terminal/models/component/endpoint.py:12 #: 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 +#: terminal/models/component/terminal.py:85 users/forms/profile.py:33 #: users/models/group.py:13 users/models/user.py:717 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -492,7 +495,7 @@ msgstr "特权账号" #: accounts/models/base.py:40 assets/models/asset/common.py:109 #: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39 #: assets/models/label.py:22 -#: authentication/serializers/connect_token_secret.py:107 +#: authentication/serializers/connect_token_secret.py:105 #: terminal/models/applet/applet.py:32 users/serializers/user.py:162 msgid "Is active" msgstr "激活" @@ -535,38 +538,36 @@ msgstr "" "{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" "密密码" -#: accounts/serializers/account/account.py:80 -#: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75 +#: accounts/serializers/account/account.py:26 +#: settings/serializers/auth/sms.py:75 msgid "Template" msgstr "模板" -#: accounts/serializers/account/account.py:83 -#: assets/serializers/asset/common.py:69 +#: accounts/serializers/account/account.py:29 msgid "Push now" msgstr "立即推送" -#: accounts/serializers/account/account.py:85 -#: accounts/serializers/account/base.py:64 -msgid "Has secret" -msgstr "已托管密码" +#: accounts/serializers/account/account.py:33 +msgid "Exist policy" +msgstr "账号存在策略" -#: accounts/serializers/account/account.py:90 applications/models.py:11 -#: assets/models/label.py:21 assets/models/platform.py:65 -#: assets/serializers/asset/common.py:131 assets/serializers/cagegory.py:8 -#: assets/serializers/platform.py:100 assets/serializers/platform.py:149 +#: accounts/serializers/account/account.py:153 applications/models.py:11 +#: assets/models/label.py:21 assets/models/platform.py:66 +#: assets/serializers/asset/common.py:103 assets/serializers/cagegory.py:8 +#: assets/serializers/platform.py:111 assets/serializers/platform.py:185 #: 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:91 +#: accounts/serializers/account/account.py:154 #: 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:66 -#: assets/serializers/asset/common.py:132 assets/serializers/platform.py:99 -#: audits/serializers.py:48 -#: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102 +#: assets/models/cmd_filter.py:74 assets/models/platform.py:67 +#: assets/serializers/asset/common.py:104 assets/serializers/platform.py:95 +#: assets/serializers/platform.py:110 audits/serializers.py:48 +#: authentication/serializers/connect_token_secret.py:114 ops/models/job.py:102 #: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:146 terminal/serializers/applet.py:29 @@ -578,15 +579,49 @@ msgstr "类别" msgid "Type" msgstr "类型" -#: accounts/serializers/account/account.py:106 +#: accounts/serializers/account/account.py:169 msgid "Asset not found" msgstr "资产不存在" -#: accounts/serializers/account/account.py:118 ops/models/base.py:19 -msgid "Account policy" -msgstr "账号策略" +#: accounts/serializers/account/account.py:175 +#: accounts/serializers/account/base.py:64 +msgid "Has secret" +msgstr "已托管密码" -#: accounts/serializers/account/account.py:185 acls/models/base.py:98 +#: accounts/serializers/account/account.py:207 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:209 +msgid "Changed" +msgstr "已修改" + +#: accounts/serializers/account/account.py:213 +#: accounts/serializers/automations/base.py:22 +#: assets/models/automations/base.py:19 +#: assets/serializers/automations/base.py:20 ops/models/base.py:17 +#: ops/models/job.py:104 ops/serializers/job.py:20 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 +msgid "Assets" +msgstr "资产" + +#: accounts/serializers/account/account.py:284 +msgid "Account already exists" +msgstr "账号已存在" + +#: accounts/serializers/account/account.py:320 +#, python-format +msgid "Asset does not support this secret type: %s" +msgstr "资产不支持账号类型: %s" + +#: accounts/serializers/account/account.py:351 +msgid "Account has exist" +msgstr "账号已存在" + +#: accounts/serializers/account/account.py:393 acls/models/base.py:98 #: acls/models/login_acl.py:13 acls/serializers/base.py:55 #: 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 @@ -604,7 +639,7 @@ msgstr "账号策略" msgid "User" msgstr "用户" -#: accounts/serializers/account/account.py:186 +#: accounts/serializers/account/account.py:394 #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:98 terminal/notifications.py:146 msgid "Date" @@ -635,19 +670,11 @@ msgstr "资产类型" msgid "Key password" msgstr "密钥密码" -#: accounts/serializers/account/base.py:81 -#: assets/serializers/asset/common.py:307 +#: accounts/serializers/account/base.py:80 +#: assets/serializers/asset/common.py:298 msgid "Spec info" msgstr "特殊信息" -#: accounts/serializers/automations/base.py:22 -#: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 ops/models/base.py:17 -#: ops/models/job.py:104 ops/serializers/job.py:20 -#: terminal/templates/terminal/_msg_command_execute_alert.html:16 -msgid "Assets" -msgstr "资产" - #: accounts/serializers/automations/base.py:23 #: assets/models/asset/common.py:108 assets/models/automations/base.py:18 #: assets/models/cmd_filter.py:32 assets/serializers/automations/base.py:21 @@ -730,11 +757,6 @@ msgstr "密码不能包含 `\"` 字符" msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" -#: accounts/validator.py:41 -#, python-brace-format -msgid "{field_name} not a legal option" -msgstr "" - #: acls/apps.py:7 msgid "Acls" msgstr "访问控制" @@ -764,7 +786,7 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)" #: acls/models/base.py:82 acls/serializers/base.py:75 #: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86 -#: authentication/serializers/connect_token_secret.py:82 +#: authentication/serializers/connect_token_secret.py:79 msgid "Reviewers" msgstr "审批人" @@ -789,7 +811,7 @@ msgid "Regex" msgstr "正则表达式" #: acls/models/command_acl.py:26 assets/models/cmd_filter.py:79 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:30 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" @@ -803,7 +825,7 @@ msgstr "忽略大小写" #: acls/models/command_acl.py:33 acls/models/command_acl.py:96 #: acls/serializers/command_acl.py:28 -#: authentication/serializers/connect_token_secret.py:79 +#: authentication/serializers/connect_token_secret.py:76 msgid "Command group" msgstr "命令组" @@ -1005,31 +1027,35 @@ msgstr "测试网关" msgid "Gather facts" msgstr "收集资产信息" -#: assets/const/category.py:11 assets/models/asset/host.py:8 +#: assets/const/category.py:10 assets/models/asset/host.py:8 #: settings/serializers/auth/radius.py:16 settings/serializers/auth/sms.py:67 #: terminal/models/component/endpoint.py:13 terminal/serializers/applet.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "主机" -#: assets/const/category.py:12 assets/models/asset/device.py:8 +#: assets/const/category.py:11 assets/models/asset/device.py:8 msgid "Device" msgstr "网络设备" -#: assets/const/category.py:13 assets/models/asset/database.py:9 -#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:119 +#: assets/const/category.py:12 assets/models/asset/database.py:9 +#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:91 msgid "Database" msgstr "数据库" -#: assets/const/category.py:14 +#: assets/const/category.py:13 msgid "Cloud service" msgstr "云服务" -#: assets/const/category.py:15 assets/models/asset/web.py:16 audits/const.py:33 +#: assets/const/category.py:14 assets/models/asset/web.py:16 audits/const.py:33 #: terminal/models/applet/applet.py:25 msgid "Web" msgstr "Web" +#: assets/const/category.py:15 +msgid "Custom type" +msgstr "自定义类型" + #: assets/const/cloud.py:7 msgid "Public cloud" msgstr "公有云" @@ -1063,7 +1089,7 @@ msgstr "防火墙" msgid "Other" msgstr "其它" -#: assets/const/types.py:200 +#: assets/const/types.py:214 msgid "All types" msgstr "所有类型" @@ -1081,7 +1107,7 @@ msgid "Basic" msgstr "基本" #: assets/const/web.py:61 assets/models/asset/web.py:13 -#: assets/serializers/asset/common.py:127 assets/serializers/platform.py:39 +#: assets/serializers/asset/common.py:99 assets/serializers/platform.py:40 msgid "Script" msgstr "脚本" @@ -1101,7 +1127,7 @@ 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:110 #: 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:158 +#: terminal/models/applet/applet.py:36 terminal/models/applet/applet.py:160 #: terminal/models/applet/host.py:111 terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:100 #: terminal/models/session/session.py:47 tickets/models/comment.py:32 @@ -1150,7 +1176,7 @@ msgid "Username same with user" msgstr "用户名与用户相同" #: assets/models/_user.py:52 authentication/models/connection_token.py:38 -#: authentication/serializers/connect_token_secret.py:104 +#: authentication/serializers/connect_token_secret.py:102 #: terminal/models/applet/applet.py:34 terminal/serializers/session.py:20 #: terminal/serializers/session.py:41 terminal/serializers/storage.py:68 msgid "Protocol" @@ -1210,19 +1236,19 @@ msgstr "云服务" msgid "Port" msgstr "端口" -#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:156 +#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:129 msgid "Address" msgstr "地址" -#: assets/models/asset/common.py:104 assets/models/platform.py:95 -#: authentication/serializers/connect_token_secret.py:108 +#: assets/models/asset/common.py:104 assets/models/platform.py:100 +#: authentication/serializers/connect_token_secret.py:106 #: perms/serializers/user_permission.py:24 -#: xpack/plugins/cloud/serializers/account_attrs.py:197 +#: xpack/plugins/cloud/serializers/account_attrs.py:187 msgid "Platform" msgstr "系统平台" #: assets/models/asset/common.py:106 assets/models/domain.py:21 -#: authentication/serializers/connect_token_secret.py:126 +#: authentication/serializers/connect_token_secret.py:124 #: perms/serializers/user_permission.py:28 msgid "Domain" msgstr "网域" @@ -1231,27 +1257,40 @@ msgstr "网域" msgid "Labels" msgstr "标签管理" -#: assets/models/asset/common.py:111 assets/serializers/asset/host.py:27 -msgid "Info" -msgstr "信息" +#: assets/models/asset/common.py:111 +#, fuzzy +#| msgid "Gather asset hardware info" +msgid "Gathered info" +msgstr "收集资产硬件信息" -#: assets/models/asset/common.py:264 +#: assets/models/asset/common.py:112 assets/serializers/asset/common.py:109 +#: assets/serializers/asset/common.py:179 +#, fuzzy +#| msgid "Auto info" +msgid "Custom info" +msgstr "自动化信息" + +#: assets/models/asset/common.py:271 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" -#: assets/models/asset/common.py:265 +#: assets/models/asset/common.py:272 msgid "Can test asset connectivity" msgstr "可以测试资产连接性" -#: assets/models/asset/common.py:266 +#: assets/models/asset/common.py:273 msgid "Can match asset" msgstr "可以匹配资产" -#: assets/models/asset/common.py:267 +#: assets/models/asset/common.py:274 msgid "Can change asset nodes" msgstr "可以修改资产节点" -#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:120 +#: assets/models/asset/custom.py:8 +msgid "Custom asset" +msgstr "自定义资产" + +#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:92 #: settings/serializers/email.py:37 msgid "Use SSL" msgstr "使用 SSL" @@ -1268,31 +1307,31 @@ msgstr "客户端证书" msgid "Client key" msgstr "客户端密钥" -#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:121 +#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:93 msgid "Allow invalid cert" msgstr "忽略证书校验" -#: assets/models/asset/web.py:9 assets/serializers/platform.py:29 +#: assets/models/asset/web.py:9 assets/serializers/platform.py:30 msgid "Autofill" msgstr "自动代填" -#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:124 -#: assets/serializers/platform.py:31 +#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:96 +#: assets/serializers/platform.py:32 msgid "Username selector" msgstr "用户名选择器" -#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:125 -#: assets/serializers/platform.py:34 +#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:97 +#: assets/serializers/platform.py:35 msgid "Password selector" msgstr "密码选择器" -#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:126 -#: assets/serializers/platform.py:37 +#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:98 +#: assets/serializers/platform.py:38 msgid "Submit selector" msgstr "确认按钮选择器" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:306 rbac/tree.py:35 +#: assets/serializers/asset/common.py:297 rbac/tree.py:35 msgid "Accounts" msgstr "账号管理" @@ -1306,7 +1345,7 @@ msgstr "资产自动化任务" #: assets/models/automations/base.py:112 audits/models.py:177 #: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:183 -#: terminal/models/applet/applet.py:157 terminal/models/applet/host.py:108 +#: terminal/models/applet/applet.py:159 terminal/models/applet/host.py:108 #: 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 @@ -1374,6 +1413,7 @@ msgid "Asset group" msgstr "资产组" #: assets/models/group.py:34 assets/models/platform.py:17 +#: assets/serializers/platform.py:97 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" msgstr "默认" @@ -1389,14 +1429,15 @@ msgstr "系统" #: assets/models/label.py:19 assets/models/node.py:544 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 #: authentication/models/connection_token.py:26 -#: authentication/serializers/connect_token_secret.py:115 +#: authentication/serializers/connect_token_secret.py:113 #: common/serializers/common.py:80 settings/models.py:34 msgid "Value" msgstr "值" -#: assets/models/label.py:40 assets/serializers/asset/common.py:133 +#: assets/models/label.py:40 assets/serializers/asset/common.py:105 #: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13 -#: authentication/serializers/connect_token_secret.py:114 +#: assets/serializers/platform.py:94 +#: authentication/serializers/connect_token_secret.py:112 #: common/serializers/common.py:79 settings/serializers/sms.py:7 msgid "Label" msgstr "标签" @@ -1438,90 +1479,98 @@ msgstr "主要的" msgid "Required" msgstr "必须的" -#: assets/models/platform.py:18 settings/serializers/settings.py:65 +#: assets/models/platform.py:18 +msgid "Public" +msgstr "开放的" + +#: assets/models/platform.py:19 settings/serializers/settings.py:65 #: users/templates/users/reset_password.html:29 msgid "Setting" msgstr "设置" -#: assets/models/platform.py:30 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:31 +#: assets/models/platform.py:32 msgid "Ansible config" msgstr "Ansible 配置" -#: assets/models/platform.py:32 assets/serializers/platform.py:60 +#: assets/models/platform.py:33 assets/serializers/platform.py:60 msgid "Ping enabled" msgstr "启用资产探活" -#: assets/models/platform.py:33 assets/serializers/platform.py:61 +#: assets/models/platform.py:34 assets/serializers/platform.py:61 msgid "Ping method" msgstr "资产探活方式" -#: assets/models/platform.py:34 assets/models/platform.py:47 +#: assets/models/platform.py:35 assets/models/platform.py:48 #: assets/serializers/platform.py:62 msgid "Gather facts enabled" msgstr "启用收集资产信息" -#: assets/models/platform.py:35 assets/models/platform.py:49 +#: assets/models/platform.py:36 assets/models/platform.py:50 #: assets/serializers/platform.py:63 msgid "Gather facts method" msgstr "收集信息方式" -#: assets/models/platform.py:36 assets/serializers/platform.py:66 +#: assets/models/platform.py:37 assets/serializers/platform.py:66 msgid "Change secret enabled" msgstr "启用改密" -#: assets/models/platform.py:38 assets/serializers/platform.py:67 +#: assets/models/platform.py:39 assets/serializers/platform.py:67 msgid "Change secret method" msgstr "改密方式" -#: assets/models/platform.py:40 assets/serializers/platform.py:68 +#: assets/models/platform.py:41 assets/serializers/platform.py:68 msgid "Push account enabled" msgstr "启用账号推送" -#: assets/models/platform.py:42 assets/serializers/platform.py:69 +#: assets/models/platform.py:43 assets/serializers/platform.py:69 msgid "Push account method" msgstr "账号推送方式" -#: assets/models/platform.py:44 assets/serializers/platform.py:64 +#: assets/models/platform.py:45 assets/serializers/platform.py:64 msgid "Verify account enabled" msgstr "开启账号验证" -#: assets/models/platform.py:46 assets/serializers/platform.py:65 +#: assets/models/platform.py:47 assets/serializers/platform.py:65 msgid "Verify account method" msgstr "账号验证方式" -#: assets/models/platform.py:67 tickets/models/ticket/general.py:300 +#: assets/models/platform.py:68 tickets/models/ticket/general.py:300 msgid "Meta" msgstr "元数据" -#: assets/models/platform.py:68 +#: assets/models/platform.py:69 msgid "Internal" msgstr "内置" -#: assets/models/platform.py:71 assets/serializers/platform.py:97 +#: assets/models/platform.py:73 assets/serializers/platform.py:109 msgid "Charset" msgstr "编码" -#: assets/models/platform.py:73 assets/serializers/platform.py:124 +#: assets/models/platform.py:75 assets/serializers/platform.py:133 msgid "Domain enabled" msgstr "启用网域" -#: assets/models/platform.py:75 assets/serializers/platform.py:123 +#: assets/models/platform.py:77 assets/serializers/platform.py:132 msgid "Su enabled" msgstr "启用账号切换" -#: assets/models/platform.py:76 assets/serializers/platform.py:106 +#: assets/models/platform.py:78 assets/serializers/platform.py:115 msgid "Su method" msgstr "账号切换方式" -#: assets/models/platform.py:78 assets/serializers/platform.py:104 +#: assets/models/platform.py:81 assets/serializers/platform.py:113 msgid "Automation" msgstr "自动化" +#: assets/models/platform.py:83 assets/serializers/platform.py:118 +msgid "Custom fields" +msgstr "自定义属性" + #: assets/models/utils.py:18 #, python-format msgid "%(value)s is not an even number" @@ -1533,36 +1582,36 @@ msgid "" "type" msgstr "资产中批量更新平台,不符合平台类型跳过的资产" -#: assets/serializers/asset/common.py:123 +#: assets/serializers/asset/common.py:95 msgid "Auto fill" msgstr "自动代填" -#: assets/serializers/asset/common.py:134 assets/serializers/platform.py:102 -#: authentication/serializers/connect_token_secret.py:28 -#: authentication/serializers/connect_token_secret.py:66 +#: assets/serializers/asset/common.py:106 assets/serializers/platform.py:112 +#: authentication/serializers/connect_token_secret.py:29 +#: authentication/serializers/connect_token_secret.py:63 #: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99 msgid "Protocols" msgstr "协议组" -#: assets/serializers/asset/common.py:136 -#: assets/serializers/asset/common.py:157 +#: assets/serializers/asset/common.py:108 +#: assets/serializers/asset/common.py:130 msgid "Node path" msgstr "节点路径" -#: assets/serializers/asset/common.py:154 -#: assets/serializers/asset/common.py:308 +#: assets/serializers/asset/common.py:127 +#: assets/serializers/asset/common.py:299 msgid "Auto info" msgstr "自动化信息" -#: assets/serializers/asset/common.py:232 +#: assets/serializers/asset/common.py:220 msgid "Platform not exist" msgstr "平台不存在" -#: assets/serializers/asset/common.py:267 +#: assets/serializers/asset/common.py:255 msgid "port out of range (1-65535)" msgstr "端口超出范围 (1-65535)" -#: assets/serializers/asset/common.py:274 +#: assets/serializers/asset/common.py:262 msgid "Protocol is required: {}" msgstr "协议是必填的: {}" @@ -1611,7 +1660,7 @@ msgid "Disk total" msgstr "硬盘大小" #: assets/serializers/asset/host.py:21 -#: authentication/serializers/connect_token_secret.py:105 +#: authentication/serializers/connect_token_secret.py:103 msgid "OS" msgstr "操作系统" @@ -1623,6 +1672,10 @@ msgstr "系统版本" msgid "OS arch" msgstr "系统架构" +#: assets/serializers/asset/host.py:27 +msgid "Info" +msgstr "信息" + #: assets/serializers/cagegory.py:9 msgid "Constraints" msgstr "约束" @@ -1651,11 +1704,11 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/platform.py:25 +#: assets/serializers/platform.py:26 msgid "SFTP enabled" msgstr "SFTP 已启用" -#: assets/serializers/platform.py:26 +#: assets/serializers/platform.py:27 msgid "SFTP home" msgstr "SFTP 根路径" @@ -1671,15 +1724,17 @@ msgstr "启用账号收集" msgid "Gather accounts method" msgstr "收集账号方式" -#: assets/serializers/platform.py:125 +#: assets/serializers/platform.py:134 msgid "Default Domain" msgstr "默认网域" -#: assets/serializers/platform.py:137 -#, fuzzy -#| msgid "Protocol is required: {}" +#: assets/serializers/platform.py:143 +msgid "type is required" +msgstr "类型 该字段是必填项。" + +#: assets/serializers/platform.py:173 msgid "Protocols is required" -msgstr "协议是必填的: {}" +msgstr "协议是必填的" #: assets/signal_handlers/asset.py:26 assets/tasks/ping.py:35 msgid "Test assets connectivity " @@ -1814,7 +1869,7 @@ msgid "Change password" msgstr "改密" #: audits/const.py:34 settings/serializers/terminal.py:6 -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:161 +#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:163 #: terminal/serializers/session.py:48 msgid "Terminal" msgstr "终端" @@ -2477,23 +2532,23 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" -#: authentication/serializers/connect_token_secret.py:106 +#: authentication/serializers/connect_token_secret.py:104 msgid "Is builtin" msgstr "内置的" -#: authentication/serializers/connect_token_secret.py:110 +#: authentication/serializers/connect_token_secret.py:108 msgid "Options" msgstr "选项" -#: authentication/serializers/connect_token_secret.py:117 +#: authentication/serializers/connect_token_secret.py:115 msgid "Component" msgstr "组件" -#: authentication/serializers/connect_token_secret.py:128 +#: authentication/serializers/connect_token_secret.py:126 msgid "Expired now" msgstr "立刻过期" -#: authentication/serializers/connect_token_secret.py:148 +#: authentication/serializers/connect_token_secret.py:145 #: authentication/templates/authentication/_access_key_modal.html:30 #: perms/models/perm_node.py:21 users/serializers/group.py:33 msgid "ID" @@ -2997,10 +3052,8 @@ msgid "Parse file error: {}" msgstr "解析文件错误: {}" #: common/drf/parsers/excel.py:14 -#, fuzzy -#| msgid "Invalid zip file" msgid "Invalid excel file" -msgstr "无效的 zip 文件" +msgstr "无效的 excel 文件" #: common/exceptions.py:15 #, python-format @@ -3383,6 +3436,10 @@ msgstr "参数" msgid "Creator" msgstr "创建者" +#: ops/models/base.py:19 +msgid "Account policy" +msgstr "账号策略" + #: ops/models/base.py:20 msgid "Last execution" msgstr "最后执行" @@ -3416,13 +3473,6 @@ msgstr "可以查看任务监控" msgid "Kwargs" msgstr "其它参数" -#: 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 "状态" - #: ops/models/celery.py:61 terminal/models/session/sharing.py:114 #: tickets/const.py:25 msgid "Finished" @@ -3934,7 +3984,7 @@ msgid "My assets" msgstr "我的资产" #: rbac/tree.py:56 terminal/models/applet/applet.py:43 -#: terminal/models/applet/applet.py:154 terminal/models/applet/host.py:28 +#: terminal/models/applet/applet.py:156 terminal/models/applet/host.py:28 #: terminal/serializers/applet.py:15 msgid "Applet" msgstr "远程应用" @@ -4432,7 +4482,7 @@ msgid "SSO auth key TTL" msgstr "令牌有效期" #: settings/serializers/auth/sso.py:17 -#: xpack/plugins/cloud/serializers/account_attrs.py:194 +#: xpack/plugins/cloud/serializers/account_attrs.py:184 msgid "Unit: second" msgstr "单位: 秒" @@ -4999,11 +5049,11 @@ msgid "" "Tip: Default resolution to use when connecting graphical assets in Luna pages" msgstr "提示:在Luna 页面中连接图形化资产时默认使用的分辨率" -#: settings/tasks/ldap.py:24 +#: settings/tasks/ldap.py:25 msgid "Import ldap user" msgstr "导入 LDAP 用户" -#: settings/tasks/ldap.py:45 +#: settings/tasks/ldap.py:47 msgid "Periodic import ldap user" msgstr "周期导入 LDAP 用户" @@ -5189,13 +5239,13 @@ msgstr "过期。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" -" 您的密码已经过期,请点击 链接 更新密码\n" +" 您的密码已经过期,请点击 链接 更新密码\n" " " #: templates/_message.html:30 @@ -5219,8 +5269,8 @@ msgstr "" #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" @@ -5232,13 +5282,13 @@ msgstr "" #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" +" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -5449,7 +5499,7 @@ msgstr "主机" msgid "Applet pkg not valid, Missing file {}" msgstr "Applet pkg 无效,缺少文件 {}" -#: terminal/models/applet/applet.py:156 terminal/models/applet/host.py:34 +#: terminal/models/applet/applet.py:158 terminal/models/applet/host.py:34 #: terminal/models/applet/host.py:106 msgid "Hosting" msgstr "宿主机" @@ -5555,28 +5605,28 @@ msgid "Default storage" msgstr "默认存储" #: terminal/models/component/storage.py:140 -#: terminal/models/component/terminal.py:90 +#: terminal/models/component/terminal.py:91 msgid "Command storage" msgstr "命令存储" #: terminal/models/component/storage.py:200 -#: terminal/models/component/terminal.py:91 +#: terminal/models/component/terminal.py:92 msgid "Replay storage" msgstr "录像存储" -#: terminal/models/component/terminal.py:87 +#: terminal/models/component/terminal.py:88 msgid "type" msgstr "类型" -#: terminal/models/component/terminal.py:89 terminal/serializers/command.py:51 +#: terminal/models/component/terminal.py:90 terminal/serializers/command.py:51 msgid "Remote Address" msgstr "远端地址" -#: terminal/models/component/terminal.py:92 +#: terminal/models/component/terminal.py:93 msgid "Application User" msgstr "应用用户" -#: terminal/models/component/terminal.py:163 +#: terminal/models/component/terminal.py:165 msgid "Can view terminal config" msgstr "可以查看终端配置" @@ -6541,10 +6591,8 @@ msgid "Avatar url" msgstr "头像路径" #: users/serializers/user.py:171 -#, fuzzy -#| msgid "One level" msgid "MFA level" -msgstr "1 级" +msgstr "MFA 级别" #: users/serializers/user.py:277 msgid "Select users" @@ -7261,13 +7309,13 @@ msgstr "JSON 格式的文件" msgid "IP address invalid `{}`, {}" msgstr "IP 地址无效: `{}`, {}" -#: xpack/plugins/cloud/serializers/account_attrs.py:172 +#: xpack/plugins/cloud/serializers/account_attrs.py:162 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" msgstr "格式为逗号分隔的字符串,如:192.168.1.0/24,10.0.0.0-10.0.0.255" -#: xpack/plugins/cloud/serializers/account_attrs.py:176 +#: xpack/plugins/cloud/serializers/account_attrs.py:166 msgid "" "The port is used to detect the validity of the IP address. When the " "synchronization task is executed, only the valid IP address will be " @@ -7276,19 +7324,19 @@ msgstr "" "端口用来检测 IP 地址的有效性,在同步任务执行时,只会同步有效的 IP 地址。
" "如果端口为 0,则表示所有 IP 地址均有效。" -#: xpack/plugins/cloud/serializers/account_attrs.py:184 +#: xpack/plugins/cloud/serializers/account_attrs.py:174 msgid "Hostname prefix" msgstr "主机名前缀" -#: xpack/plugins/cloud/serializers/account_attrs.py:187 +#: xpack/plugins/cloud/serializers/account_attrs.py:177 msgid "IP segment" msgstr "IP 网段" -#: xpack/plugins/cloud/serializers/account_attrs.py:191 +#: xpack/plugins/cloud/serializers/account_attrs.py:181 msgid "Test port" msgstr "测试端口" -#: xpack/plugins/cloud/serializers/account_attrs.py:194 +#: xpack/plugins/cloud/serializers/account_attrs.py:184 msgid "Test timeout" msgstr "测试超时时间" @@ -7368,26 +7416,31 @@ msgstr "许可证导入成功" msgid "License is invalid" msgstr "无效的许可证" -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:138 +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:135 msgid "License" msgstr "许可证" -#: xpack/plugins/license/models.py:80 +#: xpack/plugins/license/models.py:79 msgid "Standard edition" msgstr "标准版" -#: xpack/plugins/license/models.py:82 +#: xpack/plugins/license/models.py:81 msgid "Enterprise edition" msgstr "企业版" -#: xpack/plugins/license/models.py:84 +#: xpack/plugins/license/models.py:83 msgid "Ultimate edition" msgstr "旗舰版" -#: xpack/plugins/license/models.py:86 +#: xpack/plugins/license/models.py:85 msgid "Community edition" msgstr "社区版" +#, fuzzy +#~| msgid "Custom user" +#~ msgid "Custom" +#~ msgstr "自定义用户" + #~ msgid "API Server" #~ msgstr "API 服务" diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 10fb55091..116aec94e 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -64,13 +64,7 @@ class DownloadUploadMixin: if instance and not update: return Response({'error': 'Applet already exists: {}'.format(name)}, status=400) - serializer = serializers.AppletSerializer(data=manifest, instance=instance) - serializer.is_valid(raise_exception=True) - save_to = default_storage.path('applets/{}'.format(name)) - if os.path.exists(save_to): - shutil.rmtree(save_to) - shutil.move(tmp_dir, save_to) - serializer.save() + applet, serializer = Applet.install_from_dir(tmp_dir) return Response(serializer.data, status=201) @action(detail=True, methods=['get']) diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index bb6e11179..c6e938028 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -12,7 +12,6 @@ from rest_framework.serializers import ValidationError from common.db.models import JMSBaseModel from common.utils import lazyproperty, get_logger -from jumpserver.utils import has_valid_xpack_license logger = get_logger(__name__) @@ -91,24 +90,48 @@ class Applet(JMSBaseModel): return manifest @classmethod - def install_from_dir(cls, path): + def load_platform_if_need(cls, d): + from assets.serializers import PlatformSerializer + + if not os.path.exists(os.path.join(d, 'platform.yml')): + return + try: + with open(os.path.join(d, 'platform.yml')) as f: + data = yaml.safe_load(f) + except Exception as e: + raise ValidationError({'error': _('Load platform.yml failed: {}').format(e)}) + + if data['category'] != 'custom': + raise ValidationError({'error': _('Only support custom platform')}) + + try: + tp = data['type'] + except KeyError: + raise ValidationError({'error': _('Missing type in platform.yml')}) + + s = PlatformSerializer(data=data) + s.add_type_choices(tp, tp) + s.is_valid(raise_exception=True) + s.save() + + @classmethod + def install_from_dir(cls, path, builtin=True): from terminal.serializers import AppletSerializer manifest = cls.validate_pkg(path) name = manifest['name'] - if not has_valid_xpack_license() and name.lower() in ('navicat',): - return - instance = cls.objects.filter(name=name).first() serializer = AppletSerializer(instance=instance, data=manifest) serializer.is_valid() - serializer.save(builtin=True) - pkg_path = default_storage.path('applets/{}'.format(name)) + serializer.save(builtin=builtin) + cls.load_platform_if_need(path) + + pkg_path = default_storage.path('applets/{}'.format(name)) if os.path.exists(pkg_path): shutil.rmtree(pkg_path) shutil.copytree(path, pkg_path) - return instance + return instance, serializer def select_host_account(self): # 选择激活的发布机 diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index 88c2c6154..74364e06a 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,3 +1,5 @@ +import time +import uuid from django.conf import settings from django.core.cache import cache from django.db import models @@ -139,6 +141,7 @@ class Terminal(StorageMixin, TerminalStatusMixin, JMSBaseModel): if self.user: setattr(self.user, SKIP_SIGNAL, True) self.user.delete() + self.name = self.name + '_' + uuid.uuid4().hex[:8] self.user = None self.is_deleted = True self.save() From 34e846927bb9d560997b8cdaf376ad5f89b9439f Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 10 Apr 2023 11:23:21 +0800 Subject: [PATCH 082/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20connect=20?= =?UTF-8?q?token=20asset=20info?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset/common.py | 8 ++++++++ apps/authentication/api/connection_token.py | 3 ++- apps/authentication/serializers/connect_token_secret.py | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 40874915f..05fada2e3 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -148,6 +148,14 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): spec_fields = self.get_spec_fields(instance, secret=True) return self.get_spec_values(instance, spec_fields) + @lazyproperty + def info(self): + info = {} + info.update(self.gathered_info or {}) + info.update(self.custom_info or {}) + info.update(self.spec_info or {}) + return info + @lazyproperty def auto_config(self): platform = self.platform diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index dd225073e..5b5dcd700 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -162,7 +162,8 @@ class RDPFileClientProtocolURLMixin: def get_smart_endpoint(self, protocol, asset=None): target_ip = asset.get_target_ip() if asset else '' endpoint = EndpointRule.match_endpoint( - target_instance=asset, target_ip=target_ip, protocol=protocol, request=self.request + target_instance=asset, target_ip=target_ip, + protocol=protocol, request=self.request ) return endpoint diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index f5f5bc62a..9d179877e 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -27,12 +27,13 @@ class _ConnectionTokenUserSerializer(serializers.ModelSerializer): class _ConnectionTokenAssetSerializer(serializers.ModelSerializer): protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) + info = serializers.DictField() class Meta: model = Asset fields = [ 'id', 'name', 'address', 'protocols', 'category', - 'type', 'org_id', 'spec_info', 'secret_info', + 'type', 'org_id', 'info', 'secret_info', ] From 616e636837b4e3c3e736511b479a2eeef81c0141 Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 10 Apr 2023 14:19:28 +0800 Subject: [PATCH 083/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E5=8F=B7=E5=AD=97=E6=AE=B5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/serializers/fields.py | 10 ++++++++++ apps/users/serializers/user.py | 16 ++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/common/serializers/fields.py b/apps/common/serializers/fields.py index 84d4d5927..514a96fd0 100644 --- a/apps/common/serializers/fields.py +++ b/apps/common/serializers/fields.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import phonenumbers from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import gettext_lazy as _ from rest_framework import serializers @@ -17,6 +18,7 @@ __all__ = [ "BitChoicesField", "TreeChoicesField", "LabeledMultipleChoiceField", + "PhoneField", ] @@ -201,3 +203,11 @@ class BitChoicesField(TreeChoicesField): value = self.to_internal_value(data) self.run_validators(value) return value + + +class PhoneField(serializers.CharField): + def to_representation(self, value): + if value: + phone = phonenumbers.parse(value, 'CN') + value = {'code': '+%s' % phone.country_code, 'phone': phone.national_number} + return value diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 5d239820c..e21aab06b 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -8,7 +8,9 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.serializers import CommonBulkSerializerMixin -from common.serializers.fields import EncryptedField, ObjectRelatedField, LabeledChoiceField +from common.serializers.fields import ( + EncryptedField, ObjectRelatedField, LabeledChoiceField, PhoneField +) from common.utils import pretty_string, get_logger from common.validators import PhoneValidator from rbac.builtin import BuiltinRole @@ -103,6 +105,9 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer label=_("Password"), required=False, allow_blank=True, allow_null=True, max_length=1024, ) + phone = PhoneField( + validators=[PhoneValidator()], required=False, allow_null=True, label=_("Phone") + ) custom_m2m_fields = { "system_roles": [BuiltinRole.system_user], "org_roles": [BuiltinRole.org_user], @@ -169,7 +174,6 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer "created_by": {"read_only": True, "allow_blank": True}, "role": {"default": "User"}, "is_otp_secret_key_bound": {"label": _("Is OTP bound")}, - "phone": {"validators": [PhoneValidator()]}, 'mfa_level': {'label': _("MFA level")}, } @@ -224,14 +228,6 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer attrs.pop("password_strategy", None) return attrs - def to_representation(self, instance): - data = super().to_representation(instance) - phone = phonenumbers.parse(data['phone'], 'CN') - data['phone'] = { - 'code': '+%s' % phone.country_code, 'phone': phone.national_number - } - return data - def save_and_set_custom_m2m_fields(self, validated_data, save_handler, created): m2m_values = {} for f, default_roles in self.custom_m2m_fields.items(): From 907fcd755545239dee1dccc10944b5316a64874c Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 10 Apr 2023 15:18:27 +0800 Subject: [PATCH 084/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20custom=20i?= =?UTF-8?q?nfo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/custom.py | 6 +++++- apps/assets/migrations/0112_auto_20230404_1631.py | 11 +++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/assets/const/custom.py b/apps/assets/const/custom.py index be9cdcc92..47d4e9229 100644 --- a/apps/assets/const/custom.py +++ b/apps/assets/const/custom.py @@ -4,7 +4,11 @@ from .base import BaseType class CustomTypes(BaseType): @classmethod def get_choices(cls): - types = cls.get_custom_platforms().values_list('type', flat=True).distinct() + try: + platforms = list(cls.get_custom_platforms()) + except Exception: + return [] + types = [p.type for p in platforms] return [(t, t) for t in types] @classmethod diff --git a/apps/assets/migrations/0112_auto_20230404_1631.py b/apps/assets/migrations/0112_auto_20230404_1631.py index cf1c6268a..72285123a 100644 --- a/apps/assets/migrations/0112_auto_20230404_1631.py +++ b/apps/assets/migrations/0112_auto_20230404_1631.py @@ -32,14 +32,13 @@ class Migration(migrations.Migration): name='custom_info', field=models.JSONField(default=dict, verbose_name='Custom info'), ), - migrations.RenameField( - model_name='asset', - old_name='info', - new_name='gathered_info', - ), - migrations.AlterField( + migrations.AddField( model_name='asset', name='gathered_info', field=models.JSONField(blank=True, default=dict, verbose_name='Gathered info'), ), + migrations.RemoveField( + model_name='asset', + name='info', + ), ] From b58488a7e94674e98092edc89dcdae7bd1b9dd99 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Apr 2023 16:35:34 +0800 Subject: [PATCH 085/100] =?UTF-8?q?perf:=20connection=20token=20api=20?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/serializers/connect_token_secret.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index 9d179877e..b96a99171 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -33,7 +33,7 @@ class _ConnectionTokenAssetSerializer(serializers.ModelSerializer): model = Asset fields = [ 'id', 'name', 'address', 'protocols', 'category', - 'type', 'org_id', 'info', 'secret_info', + 'type', 'org_id', 'info', 'secret_info', 'spec_info' ] From 8ff1bae7e66d4be0c025440ac68a5d11b8709fb4 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 10 Apr 2023 17:24:17 +0800 Subject: [PATCH 086/100] =?UTF-8?q?fix:=20=E6=89=8B=E6=9C=BA=E5=8F=B7?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E4=B8=BA=E7=A9=BA=E5=8F=8A=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/validators.py | 10 ++++++++-- apps/users/serializers/user.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/common/validators.py b/apps/common/validators.py index c77a03581..a2e338406 100644 --- a/apps/common/validators.py +++ b/apps/common/validators.py @@ -10,6 +10,7 @@ from rest_framework.validators import ( UniqueTogetherValidator, ValidationError ) from rest_framework import serializers +from phonenumbers.phonenumberutil import NumberParseException from common.utils.strings import no_special_chars @@ -47,6 +48,11 @@ class PhoneValidator: message = _('The mobile phone number format is incorrect') def __call__(self, value): - phone = phonenumbers.parse(value) - if not phonenumbers.is_valid_number(phone): + try: + phone = phonenumbers.parse(value) + valid = phonenumbers.is_valid_number(phone) + except NumberParseException: + valid = False + + if valid: raise serializers.ValidationError(self.message) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index e21aab06b..aa997d27f 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -106,7 +106,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer allow_null=True, max_length=1024, ) phone = PhoneField( - validators=[PhoneValidator()], required=False, allow_null=True, label=_("Phone") + validators=[PhoneValidator()], required=False, allow_blank=True, allow_null=True, label=_("Phone") ) custom_m2m_fields = { "system_roles": [BuiltinRole.system_user], From b7badc146a1fc5ff5e0541d03fe019afa3bcea13 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 10 Apr 2023 17:31:53 +0800 Subject: [PATCH 087/100] fix --- apps/common/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/validators.py b/apps/common/validators.py index a2e338406..bb472dfc2 100644 --- a/apps/common/validators.py +++ b/apps/common/validators.py @@ -49,7 +49,7 @@ class PhoneValidator: def __call__(self, value): try: - phone = phonenumbers.parse(value) + phone = phonenumbers.parse(value, 'CN') valid = phonenumbers.is_valid_number(phone) except NumberParseException: valid = False From 111296ecd2dbc918fea7a0fbbfb089a6fb289e67 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 10 Apr 2023 17:57:09 +0800 Subject: [PATCH 088/100] =?UTF-8?q?fix:=20=E6=89=8B=E6=9C=BA=E5=8F=B7?= =?UTF-8?q?=E7=A0=81=E6=A0=A1=E9=AA=8C=E9=80=BB=E8=BE=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/validators.py b/apps/common/validators.py index bb472dfc2..5bb3e4bdb 100644 --- a/apps/common/validators.py +++ b/apps/common/validators.py @@ -54,5 +54,5 @@ class PhoneValidator: except NumberParseException: valid = False - if valid: + if not valid: raise serializers.ValidationError(self.message) From 36813f64dbd987d5d8aec1419b9e725f1f963b63 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 11 Apr 2023 19:27:52 +0800 Subject: [PATCH 089/100] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20device=20p?= =?UTF-8?q?latform=20=E6=94=AF=E6=8C=81=20su?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/platform.py | 2 ++ apps/common/serializers/dynamic.py | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index ba9def8b9..c5d645a46 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -90,9 +90,11 @@ class PlatformProtocolSerializer(serializers.ModelSerializer): class PlatformCustomField(serializers.Serializer): TYPE_CHOICES = [ ("str", "str"), + ("text", "text"), ("int", "int"), ("bool", "bool"), ("choice", "choice"), + ("list", "list"), ] name = serializers.CharField(label=_("Name"), max_length=128) label = serializers.CharField(label=_("Label"), max_length=128) diff --git a/apps/common/serializers/dynamic.py b/apps/common/serializers/dynamic.py index 22496dfc2..6ad47fcd8 100644 --- a/apps/common/serializers/dynamic.py +++ b/apps/common/serializers/dynamic.py @@ -11,6 +11,7 @@ type_field_map = { "bool": serializers.BooleanField, "text": serializers.CharField, "choice": serializers.ChoiceField, + "list": serializers.ListField, } From 59b27822be58300c2c49db3d168b16e89c5e5f6f Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 11 Apr 2023 19:29:56 +0800 Subject: [PATCH 090/100] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0113_auto_20230411_1917.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 apps/assets/migrations/0113_auto_20230411_1917.py diff --git a/apps/assets/migrations/0113_auto_20230411_1917.py b/apps/assets/migrations/0113_auto_20230411_1917.py new file mode 100644 index 000000000..ca41a30ab --- /dev/null +++ b/apps/assets/migrations/0113_auto_20230411_1917.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.17 on 2023-04-11 11:17 + +from django.db import migrations + + +def migrate_device_platform_su_method(apps, schema_editor): + platform_model = apps.get_model('assets', 'Platform') + device_map = { + 'Huawei': 'super', + 'Cisco': 'enable', + 'H3C': 'super_level', + } + platforms = platform_model.objects.filter(name__in=device_map.keys()) + print() + for platform in platforms: + print("Migrate platform su method: {}".format(platform.name)) + if platform.name not in device_map: + continue + platform.su_method = device_map[platform.name] + platform.su_enabled = True + platform.save(update_fields=['su_method', 'su_enabled']) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0112_auto_20230404_1631'), + ] + + operations = [ + migrations.RunPython(migrate_device_platform_su_method) + ] From a59f1895a3d9ac89ac0469daf0d4bc78671e85ed Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 12 Apr 2023 11:43:06 +0800 Subject: [PATCH 091/100] =?UTF-8?q?perf:=20=E5=BD=93=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E4=B8=BA=E7=BD=91=E5=85=B3=E6=97=B6=EF=BC=8C?= =?UTF-8?q?connection=5Ftoken=E4=B8=8D=E8=BF=94=E5=9B=9E=E7=BD=91=E5=9F=9F?= =?UTF-8?q?=E7=BD=91=E5=85=B3=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/models/connection_token.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 6a69a7e37..63049170b 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import PermissionDenied from assets.const import Protocol +from assets.const.host import GATEWAY_NAME from common.db.fields import EncryptTextField from common.exceptions import JMSException from common.utils import lazyproperty, pretty_string, bulk_get @@ -231,12 +232,14 @@ class ConnectionToken(JMSOrgBaseModel): def domain(self): if not self.asset.platform.domain_enabled: return - domain = self.asset.domain if self.asset else None + if self.asset.platform.name == GATEWAY_NAME: + return + domain = self.asset.domain if self.asset.domain else None return domain @lazyproperty def gateway(self): - if not self.asset: + if not self.asset or not self.domain: return return self.asset.gateway From 30b89e5cc93d82630e5c293fceb52b422ce8b87a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:59:13 +0800 Subject: [PATCH 092/100] =?UTF-8?q?perf:=20=E8=B4=A6=E5=8F=B7=E6=A8=A1?= =?UTF-8?q?=E7=89=88=E6=9B=B4=E6=96=B0=20(#10184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/const/account.py | 1 + apps/accounts/serializers/account/account.py | 40 ++++++-------- apps/accounts/serializers/account/template.py | 55 +++++++++++++++---- apps/assets/models/asset/common.py | 16 ++++++ 4 files changed, 76 insertions(+), 36 deletions(-) diff --git a/apps/accounts/const/account.py b/apps/accounts/const/account.py index 29185b233..55fa02d80 100644 --- a/apps/accounts/const/account.py +++ b/apps/accounts/const/account.py @@ -18,6 +18,7 @@ class AliasAccount(TextChoices): class Source(TextChoices): LOCAL = 'local', _('Local') COLLECTED = 'collected', _('Collected') + TEMPLATE = 'template', _('Template') class AccountInvalidPolicy(TextChoices): diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 0a1cf3f83..b2a982fd4 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -1,5 +1,4 @@ import uuid -from collections import defaultdict from django.db import IntegrityError from django.db.models import Q @@ -10,7 +9,7 @@ from rest_framework.validators import UniqueTogetherValidator from accounts.const import SecretType, Source, AccountInvalidPolicy from accounts.models import Account, AccountTemplate from accounts.tasks import push_accounts_to_assets_task -from assets.const import Category, AllTypes, Protocol +from assets.const import Category, AllTypes from assets.models import Asset from common.serializers import SecretReadableMixin from common.serializers.fields import ObjectRelatedField, LabeledChoiceField @@ -80,12 +79,12 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): raise serializers.ValidationError({'template': 'Template not found'}) # Set initial data from template - ignore_fields = ['id', 'name', 'date_created', 'date_updated', 'org_id'] + ignore_fields = ['id', 'date_created', 'date_updated', 'org_id'] field_names = [ field.name for field in template._meta.fields if field.name not in ignore_fields ] - attrs = {'source': 'template', 'source_id': template.id} + attrs = {'source': Source.TEMPLATE, 'source_id': str(template.id)} for name in field_names: value = getattr(template, name, None) if value is None: @@ -135,6 +134,16 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): else: raise serializers.ValidationError('Account already exists') + def validate(self, attrs): + attrs = super().validate(attrs) + if self.instance: + return attrs + + if 'source' in self.initial_data: + attrs['source'] = self.initial_data['source'] + attrs['source_id'] = self.initial_data['source_id'] + return attrs + def create(self, validated_data): push_now = validated_data.pop('push_now', None) instance, stat = self.do_create(validated_data) @@ -146,6 +155,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): validated_data.pop('username', None) validated_data.pop('on_invalid', None) push_now = validated_data.pop('push_now', None) + validated_data['source_id'] = None instance = super().update(instance, validated_data) self.push_account_if_need(instance, push_now, 'updated') return instance @@ -233,25 +243,6 @@ class AssetAccountBulkSerializer(AccountCreateUpdateSerializerMixin, serializers initial_data = self.initial_data self.from_template_if_need(initial_data) - @staticmethod - def _get_valid_secret_type_assets(assets, secret_type): - if isinstance(assets, list): - asset_ids = [a.id for a in assets] - assets = Asset.objects.filter(id__in=asset_ids) - - asset_protocol = assets.prefetch_related('protocols').values_list('id', 'protocols__name') - protocol_secret_types_map = Protocol.protocol_secret_types() - asset_secret_types_mapp = defaultdict(set) - - for asset_id, protocol in asset_protocol: - secret_types = set(protocol_secret_types_map.get(protocol, [])) - asset_secret_types_mapp[asset_id].update(secret_types) - - return [ - asset for asset in assets - if secret_type in asset_secret_types_mapp.get(asset.id, []) - ] - @staticmethod def get_filter_lookup(vd): return { @@ -314,7 +305,8 @@ class AssetAccountBulkSerializer(AccountCreateUpdateSerializerMixin, serializers vd['name'] = vd.get('username') create_handler = self.get_create_handler(on_invalid) - secret_type_supports = self._get_valid_secret_type_assets(assets, secret_type) + asset_ids = [asset.id for asset in assets] + secret_type_supports = Asset.get_secret_type_assets(asset_ids, secret_type) _results = {} for asset in assets: diff --git a/apps/accounts/serializers/account/template.py b/apps/accounts/serializers/account/template.py index a72565cf5..45190f051 100644 --- a/apps/accounts/serializers/account/template.py +++ b/apps/accounts/serializers/account/template.py @@ -1,4 +1,5 @@ -from accounts.models import AccountTemplate +from accounts.models import AccountTemplate, Account +from assets.models import Asset from common.serializers import SecretReadableMixin from .base import BaseAccountSerializer @@ -7,17 +8,47 @@ class AccountTemplateSerializer(BaseAccountSerializer): class Meta(BaseAccountSerializer.Meta): model = AccountTemplate - # @classmethod - # def validate_required(cls, attrs): - # # TODO 选择模版后检查一些必填项 - # required_field_dict = {} - # error = _('This field is required.') - # for k, v in cls().fields.items(): - # if v.required and k not in attrs: - # required_field_dict[k] = error - # if not required_field_dict: - # return - # raise serializers.ValidationError(required_field_dict) + @staticmethod + def bulk_update_accounts(instance, diff): + accounts = Account.objects.filter(source_id=instance.id) + if not accounts: + return + + secret_type = diff.pop('secret_type', None) + diff.pop('secret', None) + update_accounts = [] + for account in accounts: + for field, value in diff.items(): + setattr(account, field, value) + update_accounts.append(account) + if update_accounts: + Account.objects.bulk_update(update_accounts, diff.keys()) + + if secret_type is None: + return + + update_accounts = [] + asset_ids = accounts.values_list('asset_id', flat=True) + secret_type_supports = Asset.get_secret_type_assets(asset_ids, secret_type) + asset_ids_supports = [asset.id for asset in secret_type_supports] + for account in accounts: + asset_id = account.asset_id + if asset_id not in asset_ids_supports: + continue + account.secret_type = secret_type + account.secret = instance.secret + update_accounts.append(account) + if update_accounts: + Account.objects.bulk_update(update_accounts, ['secret', 'secret_type']) + + def update(self, instance, validated_data): + diff = { + k: v for k, v in validated_data.items() + if getattr(instance, k) != v + } + instance = super().update(instance, validated_data) + self.bulk_update_accounts(instance, diff) + return instance class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer): diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 05fada2e3..abfc1be8b 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -271,6 +271,22 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): tree_node = TreeNode(**data) return tree_node + @staticmethod + def get_secret_type_assets(asset_ids, secret_type): + assets = Asset.objects.filter(id__in=asset_ids) + asset_protocol = assets.prefetch_related('protocols').values_list('id', 'protocols__name') + protocol_secret_types_map = const.Protocol.protocol_secret_types() + asset_secret_types_mapp = defaultdict(set) + + for asset_id, protocol in asset_protocol: + secret_types = set(protocol_secret_types_map.get(protocol, [])) + asset_secret_types_mapp[asset_id].update(secret_types) + + return [ + asset for asset in assets + if secret_type in asset_secret_types_mapp.get(asset.id, []) + ] + class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset") From 4c7c8f482d2cc1594dc7d9026dcbcdd192da04e4 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 12 Apr 2023 18:13:55 +0800 Subject: [PATCH 093/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E6=89=B9=E9=87=8F=E5=91=BD=E4=BB=A4=E6=97=B6=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98=20TypeError:set=20obiect?= =?UTF-8?q?=20is=20not=20subscriptable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 3d631c06b..c41432a96 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -47,7 +47,8 @@ class JMSPermedInventory(JMSInventory): self.assets_accounts_mapper = self.get_assets_accounts_mapper() def get_asset_sorted_accounts(self, asset): - return self.assets_accounts_mapper.get(asset.id, []) + accounts = self.assets_accounts_mapper.get(asset.id, []) + return list(accounts) def get_assets_accounts_mapper(self): mapper = defaultdict(set) From 4654756966bb85eea9e29352764b888b56e022ad Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 13 Apr 2023 14:20:07 +0800 Subject: [PATCH 094/100] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E5=85=8B=E9=9A=86=20500=20(#10192)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/accounts/serializers/account/account.py | 11 ++++++----- apps/assets/serializers/asset/common.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index b2a982fd4..8bd903149 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -68,7 +68,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): @staticmethod def from_template_if_need(initial_data): - template_id = initial_data.pop('template', None) + template_id = initial_data.get('template') if not template_id: return if isinstance(template_id, (str, uuid.UUID)): @@ -84,7 +84,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): field.name for field in template._meta.fields if field.name not in ignore_fields ] - attrs = {'source': Source.TEMPLATE, 'source_id': str(template.id)} + attrs = {} for name in field_names: value = getattr(template, name, None) if value is None: @@ -139,9 +139,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): if self.instance: return attrs - if 'source' in self.initial_data: - attrs['source'] = self.initial_data['source'] - attrs['source_id'] = self.initial_data['source_id'] + template = attrs.pop('template', None) + if template: + attrs['source'] = Source.TEMPLATE + attrs['source_id'] = str(template.id) return attrs def create(self, validated_data): diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 12c127dae..45fa2ffab 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -67,6 +67,26 @@ class AssetPlatformSerializer(serializers.ModelSerializer): class AssetAccountSerializer(AccountSerializer): add_org_fields = False asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, required=False, write_only=True) + clone_id: str + + def to_internal_value(self, data): + clone_id = data.pop('id', None) + ret = super().to_internal_value(data) + self.clone_id = clone_id + return ret + + def set_secret(self, attrs): + _id = self.clone_id + if not _id: + return attrs + + account = Account.objects.get(id=_id) + attrs['secret'] = account.secret + return attrs + + def validate(self, attrs): + attrs = super().validate(attrs) + return self.set_secret(attrs) class Meta(AccountSerializer.Meta): fields = [ From 1aadb760f42f8b02591998152ae2178c9d3a708e Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:47:16 +0800 Subject: [PATCH 095/100] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E9=95=BF=E5=BA=A6=E9=99=90=E5=88=B6=E5=88=B08m=20(#10?= =?UTF-8?q?193)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aaron3S --- .../ops/migrations/0025_auto_20230413_1531.py | 33 +++++++++++++++++++ apps/ops/models/adhoc.py | 2 +- apps/ops/models/job.py | 4 +-- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 apps/ops/migrations/0025_auto_20230413_1531.py diff --git a/apps/ops/migrations/0025_auto_20230413_1531.py b/apps/ops/migrations/0025_auto_20230413_1531.py new file mode 100644 index 000000000..0b77f1281 --- /dev/null +++ b/apps/ops/migrations/0025_auto_20230413_1531.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.17 on 2023-04-13 07:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0024_alter_celerytask_date_last_publish'), + ] + + operations = [ + migrations.AlterField( + model_name='adhoc', + name='args', + field=models.CharField(default='', max_length=8192, verbose_name='Args'), + ), + migrations.AlterField( + model_name='historicaljob', + name='args', + field=models.CharField(blank=True, default='', max_length=8192, null=True, verbose_name='Args'), + ), + migrations.AlterField( + model_name='job', + name='args', + field=models.CharField(blank=True, default='', max_length=8192, null=True, verbose_name='Args'), + ), + migrations.AlterField( + model_name='jobexecution', + name='material', + field=models.CharField(blank=True, default='', max_length=8192, null=True, verbose_name='Material'), + ), + ] diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 8313be48b..254453a90 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -22,7 +22,7 @@ class AdHoc(JMSOrgBaseModel): pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all') module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, verbose_name=_('Module')) - args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) + args = models.CharField(max_length=8192, default='', verbose_name=_('Args')) creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index c41432a96..a49885dff 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -92,7 +92,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): name = models.CharField(max_length=128, null=True, verbose_name=_('Name')) instant = models.BooleanField(default=False) - args = models.CharField(max_length=1024, default='', verbose_name=_('Args'), null=True, blank=True) + args = models.CharField(max_length=8192, default='', verbose_name=_('Args'), null=True, blank=True) module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, verbose_name=_('Module'), null=True) chdir = models.CharField(default="", max_length=1024, verbose_name=_('Chdir'), null=True, blank=True) @@ -192,7 +192,7 @@ class JobExecution(JMSOrgBaseModel): date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) - material = models.CharField(max_length=1024, default='', verbose_name=_('Material'), null=True, blank=True) + material = models.CharField(max_length=8192, default='', verbose_name=_('Material'), null=True, blank=True) job_type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Material Type")) From e12b8329923aea18f6bd64e3744adf64a2013032 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 13 Apr 2023 17:26:24 +0800 Subject: [PATCH 096/100] =?UTF-8?q?perf:=20=E5=85=B3=E9=97=ADSFTP=E5=90=8E?= =?UTF-8?q?=EF=BC=8Cluna=E7=95=8C=E9=9D=A2=E4=B8=8D=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E7=9B=B8=E5=BA=94=E9=80=89=E9=A1=B9=20(#10186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 关闭SFTP后,luna界面不显示相应选项 * perf: 修改默认值 * perf: 增加资产协议冗余字段,减少关联查询 * perf: 修改 * perf: 优化 * perf: 精简 * perf: 删掉空格 * perf: 修改继承类 --- apps/assets/models/asset/common.py | 14 ++++++++++++++ apps/assets/serializers/asset/common.py | 7 ++++++- apps/perms/serializers/user_permission.py | 4 ++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index abfc1be8b..16045f3f3 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -94,6 +94,20 @@ class Protocol(models.Model): def __str__(self): return '{}/{}'.format(self.name, self.port) + @lazyproperty + def asset_platform_protocol(self): + protocols = self.asset.platform.protocols.values('name', 'public', 'setting') + protocols = list(filter(lambda p: p['name'] == self.name, protocols)) + return protocols[0] if len(protocols) > 0 else {} + + @property + def setting(self): + return self.asset_platform_protocol.get('setting', {}) + + @property + def public(self): + return self.asset_platform_protocol.get('public', True) + class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): Category = const.Category diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 45fa2ffab..2a50d3419 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -22,7 +22,7 @@ __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', 'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer', 'AssetDetailSerializer', 'DetailMixin', 'AssetAccountSerializer', - 'AccountSecretSerializer', + 'AccountSecretSerializer', 'AssetProtocolsPermsSerializer' ] uuid_pattern = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') @@ -43,6 +43,11 @@ class AssetProtocolsSerializer(serializers.ModelSerializer): fields = ['name', 'port'] +class AssetProtocolsPermsSerializer(AssetProtocolsSerializer): + class Meta(AssetProtocolsSerializer.Meta): + fields = AssetProtocolsSerializer.Meta.fields + ['public', 'setting'] + + class AssetLabelSerializer(serializers.ModelSerializer): class Meta: model = Label diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index c9582cc11..4c175cba5 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 AssetProtocolsSerializer +from assets.serializers.asset.common import AssetProtocolsPermsSerializer from common.serializers.fields import ObjectRelatedField, LabeledChoiceField from orgs.mixins.serializers import OrgResourceModelSerializerMixin from perms.serializers.permission import ActionChoicesField @@ -22,7 +22,7 @@ __all__ = [ class AssetPermedSerializer(OrgResourceModelSerializerMixin): """ 被授权资产的数据结构 """ platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) - protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) + 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')) domain = ObjectRelatedField(required=False, queryset=Node.objects, label=_('Domain')) From 8e81aee1fd411bee18dfb9d02fb8462857ae569a Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 13 Apr 2023 18:22:46 +0800 Subject: [PATCH 097/100] =?UTF-8?q?perf:=20luna=E8=BF=9E=E6=8E=A5=E6=97=B6?= =?UTF-8?q?=E4=B8=8D=E6=98=BE=E7=A4=BA=20WinRM=20=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/platform.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 08cd7e009..2eb786498 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -26,6 +26,14 @@ 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")) From 1eb8e40d3e1916e45248a882522642cff04ca260 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 13 Apr 2023 19:02:04 +0800 Subject: [PATCH 098/100] =?UTF-8?q?feat:=20=E8=B4=A6=E5=8F=B7=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E9=99=84=E5=8A=A0=E5=8F=82=E6=95=B0=20(#10080)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 账号推送附加参数 * perf: 通过节点 资产 过滤平台api * perf: push automation params * perf: 修改playbook * perf: params serializer * perf: 账号推送playbook 调整 * perf: Automation serializer add params field * perf: params 非必填 * perf: 添加is_params 给前端判断 * perf: is_params bool * perf: 修改push account ansible逻辑 * perf: 修改获取push_kwargs方法 * perf: platform migrate * perf: 修改api * perf: 单个推送 * perf: push account * perf: 修改asset auto_config --------- Co-authored-by: feng <1304903146@qq.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com> --- .../change_secret/host/aix/main.yml | 8 +- .../change_secret/host/posix/main.yml | 8 +- .../automations/change_secret/manager.py | 5 +- apps/accounts/automations/methods.py | 26 +----- .../push_account/database/mongodb/main.yml | 58 ++++++++++++ .../database/mongodb/manifest.yml | 6 ++ .../push_account/database/mysql/main.yml | 43 +++++++++ .../push_account/database/mysql/manifest.yml | 7 ++ .../push_account/database/oracle/main.yml | 44 +++++++++ .../push_account/database/oracle/manifest.yml | 6 ++ .../push_account/database/postgresql/main.yml | 46 +++++++++ .../database/postgresql/manifest.yml | 6 ++ .../push_account/database/sqlserver/main.yml | 69 ++++++++++++++ .../database/sqlserver/manifest.yml | 6 ++ .../push_account/host/aix/main.yml | 93 +++++++++++++++++++ .../push_account/host/aix/manifest.yml | 24 +++++ .../push_account/host/posix/main.yml | 93 +++++++++++++++++++ .../push_account/host/posix/manifest.yml | 25 +++++ .../push_account/host/windows/main.yml | 30 ++++++ .../push_account/host/windows/manifest.yml | 13 +++ .../automations/push_account/manager.py | 3 +- .../models/automations/push_account.py | 3 +- apps/accounts/serializers/account/account.py | 15 ++- .../serializers/automations/push_account.py | 3 +- apps/accounts/tasks/push_account.py | 3 +- apps/assets/api/platform.py | 54 ++++++++++- apps/assets/automations/base/manager.py | 29 +++++- apps/assets/automations/methods.py | 10 ++ .../migrations/0113_auto_20230411_1814.py | 66 +++++++++++++ .../migrations/0114_baseautomation_params.py | 18 ++++ apps/assets/models/asset/common.py | 13 +-- apps/assets/models/automations/base.py | 1 + apps/assets/models/platform.py | 19 +++- apps/assets/serializers/platform.py | 12 +-- apps/assets/urls/api_urls.py | 1 + apps/common/serializers/__init__.py | 1 + apps/common/serializers/dynamic.py | 2 +- 37 files changed, 799 insertions(+), 70 deletions(-) create mode 100644 apps/accounts/automations/push_account/database/mongodb/main.yml create mode 100644 apps/accounts/automations/push_account/database/mongodb/manifest.yml create mode 100644 apps/accounts/automations/push_account/database/mysql/main.yml create mode 100644 apps/accounts/automations/push_account/database/mysql/manifest.yml create mode 100644 apps/accounts/automations/push_account/database/oracle/main.yml create mode 100644 apps/accounts/automations/push_account/database/oracle/manifest.yml create mode 100644 apps/accounts/automations/push_account/database/postgresql/main.yml create mode 100644 apps/accounts/automations/push_account/database/postgresql/manifest.yml create mode 100644 apps/accounts/automations/push_account/database/sqlserver/main.yml create mode 100644 apps/accounts/automations/push_account/database/sqlserver/manifest.yml create mode 100644 apps/accounts/automations/push_account/host/aix/main.yml create mode 100644 apps/accounts/automations/push_account/host/aix/manifest.yml create mode 100644 apps/accounts/automations/push_account/host/posix/main.yml create mode 100644 apps/accounts/automations/push_account/host/posix/manifest.yml create mode 100644 apps/accounts/automations/push_account/host/windows/main.yml create mode 100644 apps/accounts/automations/push_account/host/windows/manifest.yml create mode 100644 apps/assets/migrations/0113_auto_20230411_1814.py create mode 100644 apps/assets/migrations/0114_baseautomation_params.py diff --git a/apps/accounts/automations/change_secret/host/aix/main.yml b/apps/accounts/automations/change_secret/host/aix/main.yml index 3e3daae7f..4bb571f62 100644 --- a/apps/accounts/automations/change_secret/host/aix/main.yml +++ b/apps/accounts/automations/change_secret/host/aix/main.yml @@ -18,18 +18,18 @@ - name: remove jumpserver ssh key ansible.builtin.lineinfile: - dest: "{{ kwargs.dest }}" - regexp: "{{ kwargs.regexp }}" + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" state: absent when: - account.secret_type == "ssh_key" - - kwargs.strategy == "set_jms" + - ssh_params.strategy == "set_jms" - name: Change SSH key ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" - exclusive: "{{ kwargs.exclusive }}" + exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" - name: Refresh connection diff --git a/apps/accounts/automations/change_secret/host/posix/main.yml b/apps/accounts/automations/change_secret/host/posix/main.yml index 932f3cade..8dea25c12 100644 --- a/apps/accounts/automations/change_secret/host/posix/main.yml +++ b/apps/accounts/automations/change_secret/host/posix/main.yml @@ -18,18 +18,18 @@ - name: remove jumpserver ssh key ansible.builtin.lineinfile: - dest: "{{ kwargs.dest }}" - regexp: "{{ kwargs.regexp }}" + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" state: absent when: - account.secret_type == "ssh_key" - - kwargs.strategy == "set_jms" + - ssh_params.strategy == "set_jms" - name: Change SSH key ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" - exclusive: "{{ kwargs.exclusive }}" + exclusive: "{{ ssh_params.exclusive }}" when: account.secret_type == "ssh_key" - name: Refresh connection diff --git a/apps/accounts/automations/change_secret/manager.py b/apps/accounts/automations/change_secret/manager.py index 7bad9e5f6..05e2b1349 100644 --- a/apps/accounts/automations/change_secret/manager.py +++ b/apps/accounts/automations/change_secret/manager.py @@ -42,7 +42,7 @@ class ChangeSecretManager(AccountBasePlaybookManager): def method_type(cls): return AutomationTypes.change_secret - def get_kwargs(self, account, secret, secret_type): + def get_ssh_params(self, account, secret, secret_type): kwargs = {} if secret_type != SecretType.SSH_KEY: return kwargs @@ -111,6 +111,7 @@ class ChangeSecretManager(AccountBasePlaybookManager): print(f'Windows {asset} does not support ssh key push') return inventory_hosts + host['ssh_params'] = {} for account in accounts: h = deepcopy(host) secret_type = account.secret_type @@ -129,7 +130,7 @@ class ChangeSecretManager(AccountBasePlaybookManager): private_key_path = self.generate_private_key_path(new_secret, path_dir) new_secret = self.generate_public_key(new_secret) - h['kwargs'] = self.get_kwargs(account, new_secret, secret_type) + h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type)) h['account'] = { 'name': account.name, 'username': account.username, diff --git a/apps/accounts/automations/methods.py b/apps/accounts/automations/methods.py index be5890701..557202184 100644 --- a/apps/accounts/automations/methods.py +++ b/apps/accounts/automations/methods.py @@ -1,30 +1,6 @@ import os -import copy -from accounts.const import AutomationTypes from assets.automations.methods import get_platform_automation_methods - -def copy_change_secret_to_push_account(methods): - push_account = AutomationTypes.push_account - change_secret = AutomationTypes.change_secret - copy_methods = copy.deepcopy(methods) - for method in copy_methods: - if not method['id'].startswith(change_secret): - continue - copy_method = copy.deepcopy(method) - copy_method['method'] = push_account.value - copy_method['id'] = copy_method['id'].replace( - change_secret, push_account - ) - copy_method['name'] = copy_method['name'].replace( - 'Change secret', 'Push account' - ) - methods.append(copy_method) - return methods - - BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -automation_methods = get_platform_automation_methods(BASE_DIR) - -platform_automation_methods = copy_change_secret_to_push_account(automation_methods) +platform_automation_methods = get_platform_automation_methods(BASE_DIR) diff --git a/apps/accounts/automations/push_account/database/mongodb/main.yml b/apps/accounts/automations/push_account/database/mongodb/main.yml new file mode 100644 index 000000000..42ccd78ea --- /dev/null +++ b/apps/accounts/automations/push_account/database/mongodb/main.yml @@ -0,0 +1,58 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test MongoDB connection + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + ssl: "{{ jms_asset.spec_info.use_ssl }}" + ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}" + ssl_certfile: "{{ jms_asset.secret_info.client_key }}" + connection_options: + - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" + register: db_info + + - name: Display MongoDB version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change MongoDB password + mongodb_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + ssl: "{{ jms_asset.spec_info.use_ssl }}" + ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}" + ssl_certfile: "{{ jms_asset.secret_info.client_key }}" + connection_options: + - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" + db: "{{ jms_asset.spec_info.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + mongodb_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + ssl: "{{ jms_asset.spec_info.use_ssl }}" + ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}" + ssl_certfile: "{{ jms_asset.secret_info.client_key }}" + connection_options: + - tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/accounts/automations/push_account/database/mongodb/manifest.yml b/apps/accounts/automations/push_account/database/mongodb/manifest.yml new file mode 100644 index 000000000..6c91977bf --- /dev/null +++ b/apps/accounts/automations/push_account/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_mongodb +name: Push account for MongoDB +category: database +type: + - mongodb +method: push_account diff --git a/apps/accounts/automations/push_account/database/mysql/main.yml b/apps/accounts/automations/push_account/database/mysql/main.yml new file mode 100644 index 000000000..26858c94e --- /dev/null +++ b/apps/accounts/automations/push_account/database/mysql/main.yml @@ -0,0 +1,43 @@ +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + db_name: "{{ jms_asset.spec_info.db_name }}" + + tasks: + - name: Test MySQL connection + community.mysql.mysql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version + register: db_info + + - name: MySQL version + debug: + var: db_info.version.full + + - name: Change MySQL password + community.mysql.mysql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + host: "%" + priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + community.mysql.mysql_info: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version + when: + - db_info is succeeded + - change_info is succeeded \ No newline at end of file diff --git a/apps/accounts/automations/push_account/database/mysql/manifest.yml b/apps/accounts/automations/push_account/database/mysql/manifest.yml new file mode 100644 index 000000000..712d0bfb8 --- /dev/null +++ b/apps/accounts/automations/push_account/database/mysql/manifest.yml @@ -0,0 +1,7 @@ +id: push_account_mysql +name: Push account for MySQL +category: database +type: + - mysql + - mariadb +method: push_account diff --git a/apps/accounts/automations/push_account/database/oracle/main.yml b/apps/accounts/automations/push_account/database/oracle/main.yml new file mode 100644 index 000000000..ad58e0584 --- /dev/null +++ b/apps/accounts/automations/push_account/database/oracle/main.yml @@ -0,0 +1,44 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test Oracle connection + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + mode: "{{ jms_account.mode }}" + register: db_info + + - name: Display Oracle version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change Oracle password + oracle_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + mode: "{{ jms_account.mode }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + oracle_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.spec_info.db_name }}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/accounts/automations/push_account/database/oracle/manifest.yml b/apps/accounts/automations/push_account/database/oracle/manifest.yml new file mode 100644 index 000000000..f60215892 --- /dev/null +++ b/apps/accounts/automations/push_account/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_oracle +name: Push account for Oracle +category: database +type: + - oracle +method: push_account diff --git a/apps/accounts/automations/push_account/database/postgresql/main.yml b/apps/accounts/automations/push_account/database/postgresql/main.yml new file mode 100644 index 000000000..dbb11af12 --- /dev/null +++ b/apps/accounts/automations/push_account/database/postgresql/main.yml @@ -0,0 +1,46 @@ +- hosts: postgre + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test PostgreSQL connection + community.postgresql.postgresql_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_db: "{{ jms_asset.spec_info.db_name }}" + register: result + failed_when: not result.is_available + + - name: Display PostgreSQL version + debug: + var: result.server_version.full + when: result is succeeded + + - name: Change PostgreSQL password + community.postgresql.postgresql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.spec_info.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + role_attr_flags: LOGIN + when: result is succeeded + register: change_info + + - name: Verify password + community.postgresql.postgresql_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.spec_info.db_name }}" + when: + - result is succeeded + - change_info is succeeded + register: result + failed_when: not result.is_available diff --git a/apps/accounts/automations/push_account/database/postgresql/manifest.yml b/apps/accounts/automations/push_account/database/postgresql/manifest.yml new file mode 100644 index 000000000..6488ddd5a --- /dev/null +++ b/apps/accounts/automations/push_account/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_postgresql +name: Push account for PostgreSQL +category: database +type: + - postgresql +method: push_account diff --git a/apps/accounts/automations/push_account/database/sqlserver/main.yml b/apps/accounts/automations/push_account/database/sqlserver/main.yml new file mode 100644 index 000000000..da0427f5c --- /dev/null +++ b/apps/accounts/automations/push_account/database/sqlserver/main.yml @@ -0,0 +1,69 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test SQLServer connection + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: | + SELECT @@version + register: db_info + + - name: SQLServer version + set_fact: + info: + version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}" + - debug: + var: info + + - name: Check whether SQLServer User exist + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';" + when: db_info is succeeded + register: user_exist + + - name: Change SQLServer password + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version" + when: user_exist.query_results[0] | length != 0 + register: change_info + + - name: Add SQLServer user + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version" + when: user_exist.query_results[0] | length == 0 + register: change_info + + - name: Verify password + community.general.mssql_script: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.spec_info.db_name }}' + script: | + SELECT @@version + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/accounts/automations/push_account/database/sqlserver/manifest.yml b/apps/accounts/automations/push_account/database/sqlserver/manifest.yml new file mode 100644 index 000000000..f1dc32b66 --- /dev/null +++ b/apps/accounts/automations/push_account/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_sqlserver +name: Push account for SQLServer +category: database +type: + - sqlserver +method: push_account diff --git a/apps/accounts/automations/push_account/host/aix/main.yml b/apps/accounts/automations/push_account/host/aix/main.yml new file mode 100644 index 000000000..9ac68d20e --- /dev/null +++ b/apps/accounts/automations/push_account/host/aix/main.yml @@ -0,0 +1,93 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Test privileged account + ansible.builtin.ping: + + - name: Push user + ansible.builtin.user: + name: "{{ account.username }}" + shell: "{{ params.shell }}" + home: "{{ '/home/' + account.username }}" + groups: "{{ params.groups }}" + expires: -1 + state: present + + - name: "Add {{ account.username }} group" + ansible.builtin.group: + name: "{{ account.username }}" + state: present + + - name: Check home dir exists + ansible.builtin.stat: + path: "{{ '/home/' + account.username }}" + register: home_existed + + - name: Set home dir permission + ansible.builtin.file: + path: "{{ '/home/' + account.username }}" + owner: "{{ account.username }}" + group: "{{ account.username }}" + mode: "0700" + when: + - home_existed.stat.exists == true + + - name: Add user groups + ansible.builtin.user: + name: "{{ account.username }}" + groups: "{{ params.groups }}" + when: params.groups + + - name: Push user password + ansible.builtin.user: + name: "{{ account.username }}" + password: "{{ account.secret | password_hash('sha512') }}" + update_password: always + when: account.secret_type == "password" + + - name: remove jumpserver ssh key + ansible.builtin.lineinfile: + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" + state: absent + when: + - account.secret_type == "ssh_key" + - ssh_params.strategy == "set_jms" + + - name: Push SSH key + ansible.builtin.authorized_key: + user: "{{ account.username }}" + key: "{{ account.secret }}" + exclusive: "{{ ssh_params.exclusive }}" + when: account.secret_type == "ssh_key" + + - name: Set sudo setting + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: "^{{ account.username }} ALL=" + line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" + validate: visudo -cf %s + when: + - params.sudo + + - name: Refresh connection + ansible.builtin.meta: reset_connection + + - name: Verify password + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" + ansible_become: no + when: account.secret_type == "password" + + - name: Verify SSH key + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no + when: account.secret_type == "ssh_key" diff --git a/apps/accounts/automations/push_account/host/aix/manifest.yml b/apps/accounts/automations/push_account/host/aix/manifest.yml new file mode 100644 index 000000000..ccc051eac --- /dev/null +++ b/apps/accounts/automations/push_account/host/aix/manifest.yml @@ -0,0 +1,24 @@ +id: push_account_aix +name: Push account for aix +category: host +type: + - AIX +method: push_account +params: + - name: sudo + type: str + label: 'Sudo' + default: '/bin/whoami' + help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig' + + - name: shell + type: str + label: 'Shell' + default: '/bin/bash' + + - name: groups + type: str + label: '用户组' + default: '' + help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)' + diff --git a/apps/accounts/automations/push_account/host/posix/main.yml b/apps/accounts/automations/push_account/host/posix/main.yml new file mode 100644 index 000000000..9ac68d20e --- /dev/null +++ b/apps/accounts/automations/push_account/host/posix/main.yml @@ -0,0 +1,93 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Test privileged account + ansible.builtin.ping: + + - name: Push user + ansible.builtin.user: + name: "{{ account.username }}" + shell: "{{ params.shell }}" + home: "{{ '/home/' + account.username }}" + groups: "{{ params.groups }}" + expires: -1 + state: present + + - name: "Add {{ account.username }} group" + ansible.builtin.group: + name: "{{ account.username }}" + state: present + + - name: Check home dir exists + ansible.builtin.stat: + path: "{{ '/home/' + account.username }}" + register: home_existed + + - name: Set home dir permission + ansible.builtin.file: + path: "{{ '/home/' + account.username }}" + owner: "{{ account.username }}" + group: "{{ account.username }}" + mode: "0700" + when: + - home_existed.stat.exists == true + + - name: Add user groups + ansible.builtin.user: + name: "{{ account.username }}" + groups: "{{ params.groups }}" + when: params.groups + + - name: Push user password + ansible.builtin.user: + name: "{{ account.username }}" + password: "{{ account.secret | password_hash('sha512') }}" + update_password: always + when: account.secret_type == "password" + + - name: remove jumpserver ssh key + ansible.builtin.lineinfile: + dest: "{{ ssh_params.dest }}" + regexp: "{{ ssh_params.regexp }}" + state: absent + when: + - account.secret_type == "ssh_key" + - ssh_params.strategy == "set_jms" + + - name: Push SSH key + ansible.builtin.authorized_key: + user: "{{ account.username }}" + key: "{{ account.secret }}" + exclusive: "{{ ssh_params.exclusive }}" + when: account.secret_type == "ssh_key" + + - name: Set sudo setting + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: "^{{ account.username }} ALL=" + line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}" + validate: visudo -cf %s + when: + - params.sudo + + - name: Refresh connection + ansible.builtin.meta: reset_connection + + - name: Verify password + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" + ansible_become: no + when: account.secret_type == "password" + + - name: Verify SSH key + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no + when: account.secret_type == "ssh_key" diff --git a/apps/accounts/automations/push_account/host/posix/manifest.yml b/apps/accounts/automations/push_account/host/posix/manifest.yml new file mode 100644 index 000000000..382b48add --- /dev/null +++ b/apps/accounts/automations/push_account/host/posix/manifest.yml @@ -0,0 +1,25 @@ +id: push_account_posix +name: Push account for posix +category: host +type: + - unix + - linux +method: push_account +params: + - name: sudo + type: str + label: 'Sudo' + default: '/bin/whoami' + help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig' + + - name: shell + type: str + label: 'Shell' + default: '/bin/bash' + help_text: '' + + - name: groups + type: str + label: '用户组' + default: '' + help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)' diff --git a/apps/accounts/automations/push_account/host/windows/main.yml b/apps/accounts/automations/push_account/host/windows/main.yml new file mode 100644 index 000000000..8a2a0aef0 --- /dev/null +++ b/apps/accounts/automations/push_account/host/windows/main.yml @@ -0,0 +1,30 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Test privileged account + ansible.windows.win_ping: + +# - name: Print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.secret }}" + + - name: Push user password + ansible.windows.win_user: + fullname: "{{ account.username}}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + password_never_expires: yes + groups: "{{ params.groups }}" + groups_action: add + update_password: always + when: account.secret_type == "password" + + - name: Refresh connection + ansible.builtin.meta: reset_connection + + - name: Verify password + ansible.windows.win_ping: + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" + when: account.secret_type == "password" diff --git a/apps/accounts/automations/push_account/host/windows/manifest.yml b/apps/accounts/automations/push_account/host/windows/manifest.yml new file mode 100644 index 000000000..05e3127f9 --- /dev/null +++ b/apps/accounts/automations/push_account/host/windows/manifest.yml @@ -0,0 +1,13 @@ +id: push_account_local_windows +name: Push account local account for Windows +version: 1 +method: push_account +category: host +type: + - windows +params: + - name: groups + type: str + label: '用户组' + default: 'Users,Remote Desktop Users' + help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)' diff --git a/apps/accounts/automations/push_account/manager.py b/apps/accounts/automations/push_account/manager.py index 92fde0b37..fe117f018 100644 --- a/apps/accounts/automations/push_account/manager.py +++ b/apps/accounts/automations/push_account/manager.py @@ -31,6 +31,7 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager): print(msg) return inventory_hosts + host['ssh_params'] = {} for account in accounts: h = deepcopy(host) secret_type = account.secret_type @@ -49,7 +50,7 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager): private_key_path = self.generate_private_key_path(new_secret, path_dir) new_secret = self.generate_public_key(new_secret) - h['kwargs'] = self.get_kwargs(account, new_secret, secret_type) + h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type)) h['account'] = { 'name': account.name, 'username': account.username, diff --git a/apps/accounts/models/automations/push_account.py b/apps/accounts/models/automations/push_account.py index f189a5fbd..d3a14411f 100644 --- a/apps/accounts/models/automations/push_account.py +++ b/apps/accounts/models/automations/push_account.py @@ -51,7 +51,8 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation): def to_attr_json(self): attr_json = super().to_attr_json() attr_json.update({ - 'username': self.username + 'username': self.username, + 'params': self.params, }) return attr_json diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 8bd903149..213672679 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -27,13 +27,16 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): push_now = serializers.BooleanField( default=False, label=_("Push now"), write_only=True ) + params = serializers.JSONField( + decoder=None, encoder=None, required=False, style={'base_template': 'textarea.html'} + ) on_invalid = LabeledChoiceField( choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR, write_only=True, label=_('Exist policy') ) class Meta: - fields = ['template', 'push_now', 'on_invalid'] + fields = ['template', 'push_now', 'params', 'on_invalid'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -93,10 +96,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): initial_data.update(attrs) @staticmethod - def push_account_if_need(instance, push_now, stat): + def push_account_if_need(instance, push_now, params, stat): if not push_now or stat != 'created': return - push_accounts_to_assets_task.delay([str(instance.id)]) + push_accounts_to_assets_task.delay([str(instance.id)], params) def get_validators(self): _validators = super().get_validators() @@ -147,8 +150,9 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): def create(self, validated_data): push_now = validated_data.pop('push_now', None) + params = validated_data.pop('params', None) instance, stat = self.do_create(validated_data) - self.push_account_if_need(instance, push_now, stat) + self.push_account_if_need(instance, push_now, params, stat) return instance def update(self, instance, validated_data): @@ -156,9 +160,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer): validated_data.pop('username', None) validated_data.pop('on_invalid', None) push_now = validated_data.pop('push_now', None) + params = validated_data.pop('params', None) validated_data['source_id'] = None instance = super().update(instance, validated_data) - self.push_account_if_need(instance, push_now, 'updated') + self.push_account_if_need(instance, push_now, params, 'updated') return instance diff --git a/apps/accounts/serializers/automations/push_account.py b/apps/accounts/serializers/automations/push_account.py index b9982300b..1d7bb3d36 100644 --- a/apps/accounts/serializers/automations/push_account.py +++ b/apps/accounts/serializers/automations/push_account.py @@ -7,9 +7,10 @@ from .change_secret import ( class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer): + class Meta(ChangeSecretAutomationSerializer.Meta): model = PushAccountAutomation - fields = [ + fields = ['params'] + [ n for n in ChangeSecretAutomationSerializer.Meta.fields if n not in ['recipients'] ] diff --git a/apps/accounts/tasks/push_account.py b/apps/accounts/tasks/push_account.py index 2a753cc1a..623481be9 100644 --- a/apps/accounts/tasks/push_account.py +++ b/apps/accounts/tasks/push_account.py @@ -15,7 +15,7 @@ __all__ = [ queue="ansible", verbose_name=_('Push accounts to assets'), activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None) ) -def push_accounts_to_assets_task(account_ids): +def push_accounts_to_assets_task(account_ids, params=None): from accounts.models import PushAccountAutomation from accounts.models import Account @@ -26,6 +26,7 @@ def push_accounts_to_assets_task(account_ids): task_snapshot = { 'accounts': [str(account.id) for account in accounts], 'assets': [str(account.asset_id) for account in accounts], + 'params': params or {}, } tp = AutomationTypes.push_account diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 5d5d4523c..61f4db985 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -1,10 +1,16 @@ +from rest_framework import generics +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.response import Response + from assets.const import AllTypes -from assets.models import Platform +from assets.models import Platform, Node, Asset from assets.serializers import PlatformSerializer from common.api import JMSModelViewSet +from common.permissions import IsValidUser from common.serializers import GroupedChoiceSerializer -__all__ = ['AssetPlatformViewSet'] +__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi'] class AssetPlatformViewSet(JMSModelViewSet): @@ -18,7 +24,8 @@ class AssetPlatformViewSet(JMSModelViewSet): rbac_perms = { 'categories': 'assets.view_platform', 'type_constraints': 'assets.view_platform', - 'ops_methods': 'assets.view_platform' + 'ops_methods': 'assets.view_platform', + 'filter_nodes_assets': 'assets.view_platform' } def get_queryset(self): @@ -38,3 +45,44 @@ class AssetPlatformViewSet(JMSModelViewSet): request, message={"detail": "Internal platform"} ) return super().check_object_permissions(request, obj) + + @action(methods=['post'], detail=False, url_path='filter-nodes-assets') + def filter_nodes_assets(self, request, *args, **kwargs): + node_ids = request.data.get('node_ids', []) + asset_ids = request.data.get('asset_ids', []) + nodes = Node.objects.filter(id__in=node_ids) + node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) + direct_asset_ids = Asset.objects.filter(id__in=asset_ids).values_list('id', flat=True) + platform_ids = Asset.objects.filter( + id__in=set(list(direct_asset_ids) + list(node_asset_ids)) + ).values_list('platform_id', flat=True) + platforms = Platform.objects.filter(id__in=platform_ids) + serializer = self.get_serializer(platforms, many=True) + return Response(serializer.data) + + +class PlatformAutomationMethodsApi(generics.ListAPIView): + permission_classes = (IsValidUser,) + + @staticmethod + def automation_methods(): + return AllTypes.get_automation_methods() + + def generate_serializer_fields(self): + data = self.automation_methods() + fields = { + i['id']: i['params_serializer']() + if i['params_serializer'] else None + for i in data + } + return fields + + def get_serializer_class(self): + fields = self.generate_serializer_fields() + serializer_name = 'AutomationMethodsSerializer' + return type(serializer_name, (serializers.Serializer,), fields) + + def list(self, request, *args, **kwargs): + data = self.generate_serializer_fields() + serializer = self.get_serializer(data) + return Response(serializer.data) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 63b490fd9..9207bb33e 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -1,12 +1,11 @@ import json import os import shutil -import yaml - from collections import defaultdict from hashlib import md5 from socket import gethostname +import yaml from django.conf import settings from django.utils import timezone from django.utils.translation import gettext as _ @@ -42,6 +41,26 @@ class BasePlaybookManager: self.method_hosts_mapper = defaultdict(list) self.playbooks = [] self.gateway_servers = dict() + params = self.execution.snapshot.get('params') + self.params = params or {} + + def get_params(self, automation, method_type): + method_attr = '{}_method'.format(method_type) + method_params = '{}_params'.format(method_type) + method_id = getattr(automation, method_attr) + automation_params = getattr(automation, method_params) + serializer = self.method_id_meta_mapper[method_id]['params_serializer'] + + if serializer is None: + return {} + + data = self.params.get(method_id, {}) + params = serializer(data).data + return { + field_name: automation_params.get(field_name, '') + if not params[field_name] else params[field_name] + for field_name in params + } @property def platform_automation_methods(self): @@ -102,8 +121,9 @@ class BasePlaybookManager: return host def host_callback(self, host, automation=None, **kwargs): - enabled_attr = '{}_enabled'.format(self.__class__.method_type()) - method_attr = '{}_method'.format(self.__class__.method_type()) + method_type = self.__class__.method_type() + enabled_attr = '{}_enabled'.format(method_type) + method_attr = '{}_method'.format(method_type) method_enabled = automation and \ getattr(automation, enabled_attr) and \ @@ -115,6 +135,7 @@ class BasePlaybookManager: return host host = self.convert_cert_to_file(host, kwargs.get('path_dir')) + host['params'] = self.get_params(automation, method_type) return host @staticmethod diff --git a/apps/assets/automations/methods.py b/apps/assets/automations/methods.py index e1e12fb7f..cad2b1b3c 100644 --- a/apps/assets/automations/methods.py +++ b/apps/assets/automations/methods.py @@ -22,6 +22,15 @@ def check_platform_methods(methods): raise ValueError("Duplicate id: {}".format(_id)) +def generate_serializer(data): + from common.serializers import create_serializer_class + params = data.pop('params', None) + if not params: + return None + serializer_name = data['id'].title().replace('_', '') + 'Serializer' + return create_serializer_class(serializer_name, params) + + def get_platform_automation_methods(path): methods = [] for root, dirs, files in os.walk(path, topdown=False): @@ -34,6 +43,7 @@ def get_platform_automation_methods(path): manifest = yaml.safe_load(f) check_platform_method(manifest, path) manifest['dir'] = os.path.dirname(path) + manifest['params_serializer'] = generate_serializer(manifest) methods.append(manifest) check_platform_methods(methods) diff --git a/apps/assets/migrations/0113_auto_20230411_1814.py b/apps/assets/migrations/0113_auto_20230411_1814.py new file mode 100644 index 000000000..58fd2fa92 --- /dev/null +++ b/apps/assets/migrations/0113_auto_20230411_1814.py @@ -0,0 +1,66 @@ +# Generated by Django 3.2.16 on 2023-04-11 10:14 + +from django.db import migrations, models + +from assets.const import AllTypes + + +def migrate_automation_push_account_params(apps, schema_editor): + platform_automation_model = apps.get_model('assets', 'PlatformAutomation') + platform_automation_methods = AllTypes.get_automation_methods() + methods_id_data_map = { + i['id']: None if i['params_serializer'] is None else i['params_serializer']({}).data + for i in platform_automation_methods + if i['method'] == 'push_account' + } + automation_objs = [] + for automation in platform_automation_model.objects.all(): + push_account_method = automation.push_account_method + if not push_account_method: + continue + value = methods_id_data_map.get(push_account_method) + if value is None: + continue + automation.push_account_params = value + automation_objs.append(automation) + platform_automation_model.objects.bulk_update(automation_objs, ['push_account_params']) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0112_auto_20230404_1631'), + ] + + operations = [ + migrations.AddField( + model_name='platformautomation', + name='change_secret_params', + field=models.JSONField(default=dict, verbose_name='Change secret params'), + ), + migrations.AddField( + model_name='platformautomation', + name='gather_accounts_params', + field=models.JSONField(default=dict, verbose_name='Gather facts params'), + ), + migrations.AddField( + model_name='platformautomation', + name='gather_facts_params', + field=models.JSONField(default=dict, verbose_name='Gather facts params'), + ), + migrations.AddField( + model_name='platformautomation', + name='ping_params', + field=models.JSONField(default=dict, verbose_name='Ping params'), + ), + migrations.AddField( + model_name='platformautomation', + name='push_account_params', + field=models.JSONField(default=dict, verbose_name='Push account params'), + ), + migrations.AddField( + model_name='platformautomation', + name='verify_account_params', + field=models.JSONField(default=dict, verbose_name='Verify account params'), + ), + migrations.RunPython(migrate_automation_push_account_params), + ] diff --git a/apps/assets/migrations/0114_baseautomation_params.py b/apps/assets/migrations/0114_baseautomation_params.py new file mode 100644 index 000000000..2e6d87f8e --- /dev/null +++ b/apps/assets/migrations/0114_baseautomation_params.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2023-04-13 10:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0113_auto_20230411_1814'), + ] + + operations = [ + migrations.AddField( + model_name='baseautomation', + name='params', + field=models.JSONField(default=dict, verbose_name='Params'), + ), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 16045f3f3..9c81dd0e9 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # - import json import logging from collections import defaultdict from django.db import models +from django.forms import model_to_dict from django.utils.translation import ugettext_lazy as _ from assets import const @@ -181,15 +181,8 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): } if not automation: return auto_config - auto_config.update({ - 'ping_enabled': automation.ping_enabled, - 'ansible_enabled': automation.ansible_enabled, - 'push_account_enabled': automation.push_account_enabled, - 'gather_facts_enabled': automation.gather_facts_enabled, - 'change_secret_enabled': automation.change_secret_enabled, - 'verify_account_enabled': automation.verify_account_enabled, - 'gather_accounts_enabled': automation.gather_accounts_enabled, - }) + + auto_config.update(model_to_dict(automation)) return auto_config def get_target_ip(self): diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 9ddeb5bf2..e41092fbf 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -19,6 +19,7 @@ class BaseAutomation(PeriodTaskModelMixin, JMSOrgBaseModel): assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) type = models.CharField(max_length=16, verbose_name=_('Type')) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) + params = models.JSONField(default=dict, verbose_name=_("Params")) def __str__(self): return self.name + '@' + str(self.created_by) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 2eb786498..4927ce793 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -38,25 +38,40 @@ class PlatformProtocol(models.Model): class PlatformAutomation(models.Model): ansible_enabled = models.BooleanField(default=False, verbose_name=_("Enabled")) ansible_config = models.JSONField(default=dict, verbose_name=_("Ansible config")) + ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) + ping_params = models.JSONField(default=dict, verbose_name=_("Ping params")) + gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) - gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) + gather_facts_method = models.TextField( + max_length=32, blank=True, null=True, verbose_name=_("Gather facts method") + ) + gather_facts_params = models.JSONField(default=dict, verbose_name=_("Gather facts params")) + change_secret_enabled = models.BooleanField(default=False, verbose_name=_("Change secret enabled")) change_secret_method = models.TextField( max_length=32, blank=True, null=True, verbose_name=_("Change secret method") ) + change_secret_params = models.JSONField(default=dict, verbose_name=_("Change secret params")) + push_account_enabled = models.BooleanField(default=False, verbose_name=_("Push account enabled")) push_account_method = models.TextField( max_length=32, blank=True, null=True, verbose_name=_("Push account method") ) + push_account_params = models.JSONField(default=dict, verbose_name=_("Push account params")) + verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) verify_account_method = models.TextField( - max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + max_length=32, blank=True, null=True, verbose_name=_("Verify account method") + ) + verify_account_params = models.JSONField(default=dict, verbose_name=_("Verify account params")) + gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) gather_accounts_method = models.TextField( max_length=32, blank=True, null=True, verbose_name=_("Gather facts method") ) + gather_accounts_params = models.JSONField(default=dict, verbose_name=_("Gather facts params")) class Platform(JMSBaseModel): diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index c5d645a46..c542cc20a 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -51,12 +51,12 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): fields = [ "id", "ansible_enabled", "ansible_config", - "ping_enabled", "ping_method", - "push_account_enabled", "push_account_method", - "gather_facts_enabled", "gather_facts_method", - "change_secret_enabled", "change_secret_method", - "verify_account_enabled", "verify_account_method", - "gather_accounts_enabled", "gather_accounts_method", + "ping_enabled", "ping_method", "ping_params", + "push_account_enabled", "push_account_method", "push_account_params", + "gather_facts_enabled", "gather_facts_method", "gather_facts_params", + "change_secret_enabled", "change_secret_method", "change_secret_params", + "verify_account_enabled", "verify_account_method", "verify_account_params", + "gather_accounts_enabled", "gather_accounts_method", "gather_accounts_params", ] extra_kwargs = { # 启用资产探测 diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 174dccef1..6b5f469d0 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -46,6 +46,7 @@ urlpatterns = [ path('nodes//tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'), path('gateways//test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), + path('platform-automation-methods/', api.PlatformAutomationMethodsApi.as_view(), name='platform-automation-methods'), ] urlpatterns += router.urls diff --git a/apps/common/serializers/__init__.py b/apps/common/serializers/__init__.py index 7ecadafc7..6d79241fc 100644 --- a/apps/common/serializers/__init__.py +++ b/apps/common/serializers/__init__.py @@ -1,2 +1,3 @@ from .common import * +from .dynamic import * from .mixin import * diff --git a/apps/common/serializers/dynamic.py b/apps/common/serializers/dynamic.py index 6ad47fcd8..9ab26b9bb 100644 --- a/apps/common/serializers/dynamic.py +++ b/apps/common/serializers/dynamic.py @@ -1,7 +1,7 @@ from rest_framework import serializers example_info = [ - {"name": "name", "label": "姓名", "required": False, "default": "老广", "type": "str"}, + {"name": "name", "label": "姓名", "required": False, "default": "广州老广", "type": "str"}, {"name": "age", "label": "年龄", "required": False, "default": 18, "type": "int"}, ] From 928513edd083eddb0627750c4ad097583aca396f Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 13 Apr 2023 19:16:46 +0800 Subject: [PATCH 099/100] fix: fix conflicts (#10197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: domain gateway 也添加 * fix: 不支持es8 提示 * perf: 授权过期通知 * fix: 过滤系统用户密码过滤ansible不支持的字符 * perf: 优化 apt (#8398) * pref: 修改 oracle lib path * perf: 优化 apt Co-authored-by: ibuler * fix: 修复授权过期通知bug (#8404) Co-authored-by: feng626 <1304903146@qq.com> * fix: 修改推送系统用户提示文案 * feat: add client linux arm64 version * perf: 优化签名认证 * pref: 优化没有获取到节点的问题 * fix: 修复openid用户登录时默认邮件后缀使用配置项 * fix: 修复华为短信配置错误,前端提示不对的问题 * fix: 修复账号备份失败问题 (#8852) Co-authored-by: feng626 <1304903146@qq.com> * perf: 优化加密,没有rsa则不加密 * feat: 支持对开启SSL/TLS的MongoDb数据库改密 * perf: 工单新增相关过滤 * fix: 修复配置mfa失效日期 失效问题 (#8856) Co-authored-by: feng626 <1304903146@qq.com> * fix: 修复日志记录到syslog时中文编码问题 * workflow: 修改 Gitee 同步的目的仓库 * fix: 修复导出账号历史翻译信息 --------- Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com> Co-authored-by: 老广 Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: Jiangjie.Bai Co-authored-by: jiangweidong Co-authored-by: Bai Co-authored-by: BugKing From 21da805e787b47637aaf82ea9c293229d752a5f2 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 13 Apr 2023 19:19:20 +0800 Subject: [PATCH 100/100] fix: fix conflicts --- apps/accounts/serializers/account/account.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/accounts/serializers/account/account.py b/apps/accounts/serializers/account/account.py index 213672679..7a4d90919 100644 --- a/apps/accounts/serializers/account/account.py +++ b/apps/accounts/serializers/account/account.py @@ -385,10 +385,7 @@ class AccountHistorySerializer(serializers.ModelSerializer): class Meta: model = Account.history.model - fields = [ - 'id', 'secret', 'secret_type', 'version', - 'history_date', 'history_user' - ] + fields = ['id', 'secret', 'secret_type', 'version', 'history_date', 'history_user'] read_only_fields = fields extra_kwargs = { 'history_user': {'label': _('User')},