From 6233e2b3de4cdb7910a068e9532ccb519ead9684 Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 6 Dec 2022 19:41:26 +0800 Subject: [PATCH 001/132] =?UTF-8?q?perf:=20Account=20su=5Ffrom=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E4=BD=BF=E7=94=A8=20ObjectRelatedField=20allow=5Fnull?= =?UTF-8?q?=20allow=5Fempty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index dfad8590f..69c4072e8 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -59,7 +59,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id') ) su_from = ObjectRelatedField( - required=False, queryset=Account.objects, + required=False, queryset=Account.objects, allow_null=True, allow_empty=True, label=_('Account'), attrs=('id', 'name', 'username') ) From d92b198a12c3249de30b3db3c6129150843824e5 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 6 Dec 2022 17:30:54 +0800 Subject: [PATCH 002/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96ops=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/adhoc.py | 12 +++--------- apps/ops/api/base.py | 17 +++++++++++++++++ apps/ops/api/job.py | 12 +++++++----- apps/ops/api/playbook.py | 3 ++- apps/ops/tasks.py | 22 ++++++++++------------ apps/ops/utils.py | 2 +- 6 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 apps/ops/api/base.py diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 2713d448f..158efc045 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -1,9 +1,5 @@ # -*- coding: utf-8 -*- -# - -from rest_framework_bulk import BulkModelViewSet - -from common.mixins import CommonApiMixin +from .base import SelfBulkModelViewSet from ..models import AdHoc from ..serializers import ( AdHocSerializer @@ -14,9 +10,7 @@ __all__ = [ ] -class AdHocViewSet(CommonApiMixin, BulkModelViewSet): +class AdHocViewSet(SelfBulkModelViewSet): serializer_class = AdHocSerializer permission_classes = () - - def get_queryset(self): - return AdHoc.objects.filter(creator=self.request.user) + model = AdHoc diff --git a/apps/ops/api/base.py b/apps/ops/api/base.py new file mode 100644 index 000000000..c04e85e38 --- /dev/null +++ b/apps/ops/api/base.py @@ -0,0 +1,17 @@ +from rest_framework_bulk import BulkModelViewSet + +from common.mixins import CommonApiMixin + +__all__ = ['SelfBulkModelViewSet'] + + +class SelfBulkModelViewSet(CommonApiMixin, BulkModelViewSet): + + def get_queryset(self): + if hasattr(self, 'model'): + return self.model.objects.filter(creator=self.request.user) + else: + assert self.queryset is None, ( + "'%s' should not include a `queryset` attribute" + % self.__class__.__name__ + ) diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index a7ba44f22..b5097a037 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -2,6 +2,7 @@ from rest_framework import viewsets from rest_framework_bulk import BulkModelViewSet from common.mixins import CommonApiMixin +from ops.api.base import SelfBulkModelViewSet from ops.models import Job, JobExecution from ops.serializers.job import JobSerializer, JobExecutionSerializer @@ -16,12 +17,13 @@ def set_task_to_serializer_data(serializer, task): setattr(serializer, "_data", data) -class JobViewSet(CommonApiMixin, BulkModelViewSet): +class JobViewSet(SelfBulkModelViewSet): serializer_class = JobSerializer permission_classes = () + model = Job def get_queryset(self): - query_set = Job.objects.filter(creator=self.request.user) + query_set = super().get_queryset() if self.action != 'retrieve': return query_set.filter(instant=False) return query_set @@ -45,10 +47,11 @@ class JobViewSet(CommonApiMixin, BulkModelViewSet): set_task_to_serializer_data(serializer, task) -class JobExecutionViewSet(CommonApiMixin, BulkModelViewSet): +class JobExecutionViewSet(SelfBulkModelViewSet): serializer_class = JobExecutionSerializer http_method_names = ('get', 'post', 'head', 'options',) permission_classes = () + model = JobExecution def perform_create(self, serializer): instance = serializer.save() @@ -56,8 +59,7 @@ class JobExecutionViewSet(CommonApiMixin, BulkModelViewSet): set_task_to_serializer_data(serializer, task) def get_queryset(self): - query_set = JobExecution.objects.filter(creator=self.request.user) - query_set = query_set.filter(creator=self.request.user) + query_set = super().get_queryset() job_id = self.request.query_params.get('job_id') if job_id: query_set = query_set.filter(job_id=job_id) diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index 9ce525c28..2d3d33e6b 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -6,6 +6,7 @@ from rest_framework_bulk import BulkModelViewSet from common.mixins import CommonApiMixin from orgs.mixins.api import OrgBulkModelViewSet +from .base import SelfBulkModelViewSet from ..exception import PlaybookNoValidEntry from ..models import Playbook from ..serializers.playbook import PlaybookSerializer @@ -19,7 +20,7 @@ def unzip_playbook(src, dist): fz.extract(file, dist) -class PlaybookViewSet(CommonApiMixin, BulkModelViewSet): +class PlaybookViewSet(SelfBulkModelViewSet): serializer_class = PlaybookSerializer permission_classes = () model = Playbook diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index bd4d5d448..bc206cc27 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -28,23 +28,21 @@ logger = get_logger(__file__) @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) def run_ops_job(job_id): job = get_object_or_none(Job, id=job_id) - with tmp_to_org(job.org): - execution = job.create_execution() - run_ops_job_execution(execution) + execution = job.create_execution() + run_ops_job_execution(execution) @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task execution")) def run_ops_job_execution(execution_id, **kwargs): execution = get_object_or_none(JobExecution, id=execution_id) - with tmp_to_org(execution.org): - try: - execution.start() - except SoftTimeLimitExceeded: - execution.set_error('Run timeout') - logger.error("Run adhoc timeout") - except Exception as e: - execution.set_error(e) - logger.error("Start adhoc execution error: {}".format(e)) + try: + execution.start() + except SoftTimeLimitExceeded: + execution.set_error('Run timeout') + logger.error("Run adhoc timeout") + except Exception as e: + execution.set_error(e) + logger.error("Start adhoc execution error: {}".format(e)) @shared_task(verbose_name=_('Periodic clear celery tasks')) diff --git a/apps/ops/utils.py b/apps/ops/utils.py index 456e73d00..e8e6cadca 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -4,7 +4,7 @@ import uuid from django.utils.translation import ugettext_lazy as _ -from common.utils import get_logger, get_object_or_none +from common.utils import get_logger, get_object_or_none, make_dirs from orgs.utils import org_aware_func from jumpserver.const import PROJECT_DIR From 07b3774d3d82d5cb4f5f02381b76db494297486e Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 6 Dec 2022 19:43:32 +0800 Subject: [PATCH 003/132] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E5=86=85=E7=BD=AE=E5=8F=98=E9=87=8F,?= =?UTF-8?q?=E4=BC=98=E5=8C=96self=E8=B5=84=E6=BA=90=E7=9A=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 6996 ++++++++++++++++++++++++++ apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 6995 +++++++++++++++++++++++++ apps/ops/api/job.py | 17 +- apps/ops/models/job.py | 12 + apps/ops/serializers/job.py | 1 + apps/ops/urls/api_urls.py | 1 + apps/ops/variables.py | 33 + 9 files changed, 14055 insertions(+), 8 deletions(-) create mode 100644 apps/locale/ja/LC_MESSAGES/django.po create mode 100644 apps/locale/zh/LC_MESSAGES/django.po create mode 100644 apps/ops/variables.py diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 54a75e2ed..315c546b7 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:0818af791dad7cd50e19c41de0bc8967f9d08f949f48d5c2020786153a743349 -size 116392 +oid sha256:5cc8f923c01a87b106a54f8a7c53abdb98683b1c4b4f975f9a3ae8af5fae73c8 +size 373 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po new file mode 100644 index 000000000..a892b45d6 --- /dev/null +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -0,0 +1,6996 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-06 17:33+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: acls/apps.py:7 +msgid "Acls" +msgstr "" + +#: acls/models/base.py:20 tickets/const.py:45 +#: tickets/templates/tickets/approve_check_password.html:49 +msgid "Reject" +msgstr "" + +#: acls/models/base.py:21 +msgid "Accept" +msgstr "" + +#: acls/models/base.py:22 +msgid "Review" +msgstr "" + +#: acls/models/base.py:71 acls/models/command_acl.py:22 +#: acls/serializers/base.py:34 applications/models.py:10 +#: assets/models/_user.py:33 assets/models/asset/common.py:81 +#: assets/models/asset/common.py:91 assets/models/base.py:64 +#: assets/models/cmd_filter.py:26 assets/models/domain.py:21 +#: assets/models/group.py:20 assets/models/label.py:17 +#: assets/models/platform.py:21 assets/models/platform.py:72 +#: assets/serializers/asset/common.py:87 assets/serializers/platform.py:139 +#: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 +#: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 +#: perms/models/asset_permission.py:51 rbac/models/role.py:29 +#: settings/models.py:33 settings/serializers/sms.py:6 +#: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:12 +#: terminal/models/component/endpoint.py:86 +#: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 +#: terminal/models/component/terminal.py:80 users/forms/profile.py:33 +#: users/models/group.py:15 users/models/user.py:675 +#: xpack/plugins/cloud/models.py:30 +msgid "Name" +msgstr "" + +#: acls/models/base.py:73 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:89 +msgid "Priority" +msgstr "" + +#: acls/models/base.py:74 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:90 +msgid "1-100, the lower the value will be match first" +msgstr "" + +#: acls/models/base.py:77 acls/serializers/base.py:63 +#: assets/models/cmd_filter.py:86 audits/models.py:51 audits/serializers.py:75 +#: authentication/templates/authentication/_access_key_modal.html:34 +msgid "Action" +msgstr "" + +#: acls/models/base.py:78 acls/serializers/base.py:59 +#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:91 +#: authentication/serializers/connect_token_secret.py:79 +msgid "Reviewers" +msgstr "" + +#: acls/models/base.py:79 authentication/models/access_key.py:17 +#: authentication/templates/authentication/_access_key_modal.html:32 +#: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 +#: tickets/const.py:37 +msgid "Active" +msgstr "" + +#: acls/models/base.py:80 acls/models/command_acl.py:29 +#: applications/models.py:19 assets/models/_user.py:40 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:22 +#: assets/models/backup.py:29 assets/models/base.py:72 +#: assets/models/cmd_filter.py:45 assets/models/cmd_filter.py:93 +#: assets/models/domain.py:22 assets/models/group.py:23 +#: assets/models/label.py:22 assets/models/platform.py:77 +#: ops/models/adhoc.py:27 ops/models/job.py:50 ops/models/playbook.py:17 +#: orgs/models.py:74 perms/models/asset_permission.py:71 rbac/models/role.py:37 +#: settings/models.py:38 terminal/models/applet/applet.py:28 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 +#: terminal/models/component/endpoint.py:20 +#: terminal/models/component/endpoint.py:96 +#: terminal/models/component/storage.py:28 +#: terminal/models/component/terminal.py:92 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:296 users/models/group.py:16 +#: users/models/user.py:714 xpack/plugins/change_auth_plan/models/base.py:44 +#: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:121 +#: xpack/plugins/gathered_user/models.py:26 +msgid "Comment" +msgstr "" + +#: acls/models/base.py:92 acls/models/login_acl.py:13 +#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 +#: assets/models/cmd_filter.py:29 assets/models/label.py:15 audits/models.py:30 +#: audits/models.py:49 audits/models.py:93 +#: authentication/models/connection_token.py:25 +#: authentication/models/sso_token.py:16 +#: notifications/models/notification.py:12 +#: perms/api/user_permission/mixin.py:69 perms/models/asset_permission.py:53 +#: perms/models/perm_token.py:12 rbac/builtin.py:120 +#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20 +#: terminal/backends/command/serializers.py:13 +#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 +#: terminal/notifications.py:94 terminal/notifications.py:142 +#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:907 +#: users/models/user.py:938 users/serializers/group.py:19 +msgid "User" +msgstr "" + +#: acls/models/base.py:94 acls/serializers/base.py:56 +#: assets/models/account.py:51 assets/models/asset/common.py:83 +#: assets/models/asset/common.py:212 assets/models/cmd_filter.py:41 +#: assets/models/gathered_user.py:14 assets/serializers/account/account.py:59 +#: assets/serializers/automations/change_secret.py:100 +#: assets/serializers/automations/change_secret.py:122 +#: assets/serializers/domain.py:19 assets/serializers/gathered_user.py:11 +#: assets/serializers/label.py:30 audits/models.py:34 +#: authentication/models/connection_token.py:29 +#: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 +#: terminal/backends/command/models.py:21 +#: terminal/backends/command/serializers.py:14 +#: terminal/models/session/session.py:32 terminal/notifications.py:93 +#: xpack/plugins/change_auth_plan/models/asset.py:200 +#: xpack/plugins/change_auth_plan/serializers/asset.py:172 +#: xpack/plugins/cloud/models.py:222 +msgid "Asset" +msgstr "" + +#: acls/models/base.py:96 acls/serializers/base.py:57 +#: assets/models/account.py:61 +#: assets/serializers/automations/change_secret.py:101 +#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 +#: perms/models/perm_token.py:14 terminal/backends/command/models.py:22 +#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 +#: xpack/plugins/cloud/serializers/task.py:71 +msgid "Account" +msgstr "" + +#: acls/models/command_acl.py:17 assets/models/cmd_filter.py:65 +#: terminal/backends/command/serializers.py:15 +#: terminal/models/session/session.py:41 +#: terminal/templates/terminal/_msg_command_alert.html:12 +#: terminal/templates/terminal/_msg_command_execute_alert.html:10 +msgid "Command" +msgstr "" + +#: acls/models/command_acl.py:18 assets/models/cmd_filter.py:64 +msgid "Regex" +msgstr "" + +#: acls/models/command_acl.py:25 acls/serializers/command_acl.py:14 +#: applications/models.py:15 assets/models/_user.py:46 +#: assets/models/automations/base.py:20 assets/models/cmd_filter.py:79 +#: assets/models/platform.py:74 assets/serializers/asset/common.py:63 +#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:99 +#: audits/serializers.py:40 ops/models/job.py:42 +#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 +#: terminal/models/component/storage.py:57 +#: terminal/models/component/storage.py:146 terminal/serializers/applet.py:33 +#: tickets/models/comment.py:26 tickets/models/flow.py:57 +#: tickets/models/ticket/apply_application.py:16 +#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 +#: tickets/serializers/ticket/ticket.py:19 +#: xpack/plugins/change_auth_plan/models/app.py:27 +#: xpack/plugins/change_auth_plan/models/app.py:152 +msgid "Type" +msgstr "" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 +msgid "Content" +msgstr "" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 +msgid "One line one command" +msgstr "" + +#: acls/models/command_acl.py:28 assets/models/cmd_filter.py:85 +msgid "Ignore case" +msgstr "" + +#: acls/models/command_acl.py:35 acls/serializers/command_acl.py:24 +#: authentication/serializers/connect_token_secret.py:76 +msgid "Command group" +msgstr "" + +#: acls/models/command_acl.py:88 +msgid "The generated regular expression is incorrect: {}" +msgstr "" + +#: acls/models/command_acl.py:98 +msgid "Commands" +msgstr "" + +#: acls/models/command_acl.py:102 +msgid "Command acl" +msgstr "" + +#: acls/models/command_acl.py:111 tickets/const.py:11 +msgid "Command confirm" +msgstr "" + +#: acls/models/login_acl.py:16 +msgid "Rule" +msgstr "" + +#: acls/models/login_acl.py:19 +msgid "Login acl" +msgstr "" + +#: acls/models/login_acl.py:54 tickets/const.py:10 +msgid "Login confirm" +msgstr "" + +#: acls/models/login_asset_acl.py:10 +msgid "Login asset acl" +msgstr "" + +#: acls/models/login_asset_acl.py:20 tickets/const.py:12 +msgid "Login asset confirm" +msgstr "" + +#: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 +msgid "Format for comma-delimited string, with * indicating a match all. " +msgstr "" + +#: acls/serializers/base.py:18 acls/serializers/base.py:49 +#: assets/models/_user.py:34 assets/models/base.py:65 +#: assets/models/gathered_user.py:15 audits/models.py:109 +#: authentication/forms.py:25 authentication/forms.py:27 +#: authentication/models/temp_token.py:9 +#: authentication/templates/authentication/_msg_different_city.html:9 +#: authentication/templates/authentication/_msg_oauth_bind.html:9 +#: users/forms/profile.py:32 users/forms/profile.py:112 +#: users/models/user.py:673 users/templates/users/_msg_user_created.html:12 +#: xpack/plugins/change_auth_plan/models/asset.py:35 +#: xpack/plugins/change_auth_plan/models/asset.py:196 +#: xpack/plugins/cloud/serializers/account_attrs.py:26 +msgid "Username" +msgstr "" + +#: acls/serializers/base.py:25 +msgid "" +"Format for comma-delimited string, with * indicating a match all. Such as: " +"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" +"db8:1a:1110::/64 (Domain name support)" +msgstr "" + +#: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 +msgid "IP/Host" +msgstr "" + +#: acls/serializers/base.py:90 tickets/serializers/ticket/ticket.py:79 +msgid "The organization `{}` does not exist" +msgstr "" + +#: acls/serializers/base.py:96 +msgid "None of the reviewers belong to Organization `{}`" +msgstr "" + +#: acls/serializers/rules/rules.py:20 +#: xpack/plugins/cloud/serializers/task.py:23 +msgid "IP address invalid: `{}`" +msgstr "" + +#: acls/serializers/rules/rules.py:25 +msgid "" +"Format for comma-delimited string, with * indicating a match all. Such as: " +"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" +"db8:1a:1110::/64 " +msgstr "" + +#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 +#: 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:54 +msgid "IP" +msgstr "" + +#: acls/serializers/rules/rules.py:35 +msgid "Time Period" +msgstr "" + +#: applications/apps.py:9 +msgid "Applications" +msgstr "" + +#: applications/models.py:12 assets/models/label.py:20 +#: assets/models/platform.py:73 assets/serializers/asset/common.py:62 +#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:100 +#: assets/serializers/platform.py:140 perms/serializers/user_permission.py:23 +#: settings/models.py:35 tickets/models/ticket/apply_application.py:13 +#: xpack/plugins/change_auth_plan/models/app.py:24 +msgid "Category" +msgstr "" + +#: applications/models.py:17 xpack/plugins/cloud/models.py:35 +#: xpack/plugins/cloud/serializers/account.py:64 +msgid "Attrs" +msgstr "" + +#: applications/models.py:23 xpack/plugins/change_auth_plan/models/app.py:31 +msgid "Application" +msgstr "" + +#: applications/models.py:27 +msgid "Can match application" +msgstr "" + +#: applications/serializers/attrs/application_type/clickhouse.py:11 +#: assets/models/asset/common.py:82 assets/models/platform.py:22 +#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 +#: xpack/plugins/cloud/serializers/account_attrs.py:73 +msgid "Port" +msgstr "" + +#: applications/serializers/attrs/application_type/clickhouse.py:13 +msgid "" +"Typically, the port is 9000,the HTTP interface and the native interface use " +"different ports" +msgstr "" + +#: assets/api/automations/base.py:76 +#: xpack/plugins/change_auth_plan/api/asset.py:94 +msgid "The parameter 'action' must be [{}]" +msgstr "" + +#: assets/api/domain.py:56 +msgid "Number required" +msgstr "" + +#: assets/api/node.py:62 +msgid "You can't update the root node name" +msgstr "" + +#: assets/api/node.py:69 +msgid "You can't delete the root node ({})" +msgstr "" + +#: assets/api/node.py:72 +msgid "Deletion failed and the node contains assets" +msgstr "" + +#: assets/apps.py:9 +msgid "App assets" +msgstr "" + +#: assets/automations/base/manager.py:123 +msgid "{} disabled" +msgstr "" + +#: assets/const/account.py:6 audits/const.py:6 audits/const.py:64 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 +msgid "Unknown" +msgstr "" + +#: assets/const/account.py:7 +msgid "Ok" +msgstr "" + +#: assets/const/account.py:8 +#: assets/serializers/automations/change_secret.py:118 +#: assets/serializers/automations/change_secret.py:146 audits/const.py:75 +#: common/const/choices.py:19 +#: xpack/plugins/change_auth_plan/serializers/asset.py:190 +#: xpack/plugins/cloud/const.py:41 +msgid "Failed" +msgstr "" + +#: assets/const/account.py:12 assets/models/_user.py:35 +#: audits/signal_handlers.py:49 authentication/confirm/password.py:9 +#: authentication/forms.py:32 +#: authentication/templates/authentication/login.html:228 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 +#: users/forms/profile.py:22 users/serializers/user.py:105 +#: users/templates/users/_msg_user_created.html:13 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models/base.py:42 +#: xpack/plugins/change_auth_plan/models/base.py:117 +#: xpack/plugins/change_auth_plan/models/base.py:192 +#: xpack/plugins/change_auth_plan/serializers/base.py:21 +#: xpack/plugins/change_auth_plan/serializers/base.py:73 +#: xpack/plugins/cloud/serializers/account_attrs.py:28 +msgid "Password" +msgstr "" + +#: assets/const/account.py:13 +msgid "SSH key" +msgstr "" + +#: assets/const/account.py:14 authentication/models/access_key.py:33 +msgid "Access key" +msgstr "" + +#: assets/const/account.py:15 assets/models/_user.py:38 +#: authentication/models/sso_token.py:14 +msgid "Token" +msgstr "" + +#: assets/const/automation.py:13 +msgid "Ping" +msgstr "" + +#: assets/const/automation.py:14 +msgid "Gather facts" +msgstr "" + +#: assets/const/automation.py:15 +msgid "Create account" +msgstr "" + +#: assets/const/automation.py:16 +msgid "Change secret" +msgstr "" + +#: assets/const/automation.py:17 +msgid "Verify account" +msgstr "" + +#: assets/const/automation.py:18 +msgid "Gather accounts" +msgstr "" + +#: assets/const/automation.py:38 assets/serializers/account/base.py:26 +msgid "Specific" +msgstr "" + +#: assets/const/automation.py:39 ops/const.py:20 +#: xpack/plugins/change_auth_plan/models/base.py:28 +msgid "All assets use the same random password" +msgstr "" + +#: assets/const/automation.py:40 ops/const.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:29 +msgid "All assets use different random password" +msgstr "" + +#: assets/const/automation.py:44 ops/const.py:13 +#: xpack/plugins/change_auth_plan/models/asset.py:30 +msgid "Append SSH KEY" +msgstr "" + +#: assets/const/automation.py:45 ops/const.py:14 +#: xpack/plugins/change_auth_plan/models/asset.py:31 +msgid "Empty and append SSH KEY" +msgstr "" + +#: assets/const/automation.py:46 ops/const.py:15 +#: xpack/plugins/change_auth_plan/models/asset.py:32 +msgid "Replace (The key generated by JumpServer) " +msgstr "" + +#: assets/const/category.py:11 settings/serializers/auth/radius.py:16 +#: settings/serializers/auth/sms.py:67 terminal/models/applet/applet.py:59 +#: terminal/models/component/endpoint.py:13 +#: xpack/plugins/cloud/serializers/account_attrs.py:72 +msgid "Host" +msgstr "" + +#: assets/const/category.py:12 +msgid "Device" +msgstr "" + +#: assets/const/category.py:13 assets/models/asset/database.py:8 +#: assets/models/asset/database.py:34 +msgid "Database" +msgstr "" + +#: assets/const/category.py:14 +msgid "Cloud service" +msgstr "" + +#: assets/const/category.py:15 audits/const.py:62 +#: terminal/models/applet/applet.py:18 +msgid "Web" +msgstr "" + +#: assets/const/device.py:7 terminal/models/applet/applet.py:17 +#: tickets/const.py:8 +msgid "General" +msgstr "" + +#: assets/const/device.py:8 +msgid "Switch" +msgstr "" + +#: assets/const/device.py:9 +msgid "Router" +msgstr "" + +#: assets/const/device.py:10 +msgid "Firewall" +msgstr "" + +#: assets/const/web.py:7 +msgid "Website" +msgstr "" + +#: assets/models/_user.py:24 +msgid "Automatic managed" +msgstr "" + +#: assets/models/_user.py:25 +msgid "Manually input" +msgstr "" + +#: assets/models/_user.py:29 +msgid "Common user" +msgstr "" + +#: assets/models/_user.py:30 +msgid "Admin user" +msgstr "" + +#: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 +#: xpack/plugins/change_auth_plan/models/asset.py:131 +#: xpack/plugins/change_auth_plan/models/asset.py:207 +msgid "SSH private key" +msgstr "" + +#: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 +#: xpack/plugins/change_auth_plan/models/asset.py:127 +#: xpack/plugins/change_auth_plan/models/asset.py:203 +msgid "SSH public key" +msgstr "" + +#: assets/models/_user.py:41 assets/models/automations/base.py:92 +#: assets/models/cmd_filter.py:46 assets/models/domain.py:23 +#: assets/models/gathered_user.py:19 assets/models/group.py:22 +#: common/db/models.py:77 common/mixins/models.py:50 ops/models/base.py:54 +#: ops/models/job.py:108 orgs/models.py:73 perms/models/asset_permission.py:74 +#: users/models/group.py:18 users/models/user.py:939 +#: xpack/plugins/change_auth_plan/models/base.py:45 +msgid "Date created" +msgstr "" + +#: assets/models/_user.py:42 assets/models/cmd_filter.py:47 +#: assets/models/gathered_user.py:20 common/db/models.py:78 +#: common/mixins/models.py:51 xpack/plugins/change_auth_plan/models/base.py:46 +msgid "Date updated" +msgstr "" + +#: assets/models/_user.py:43 assets/models/base.py:73 +#: assets/models/cmd_filter.py:49 assets/models/cmd_filter.py:96 +#: assets/models/group.py:21 common/db/models.py:75 common/mixins/models.py:49 +#: orgs/models.py:71 perms/models/asset_permission.py:75 +#: users/models/user.py:722 users/serializers/group.py:33 +#: xpack/plugins/change_auth_plan/models/base.py:48 +msgid "Created by" +msgstr "" + +#: assets/models/_user.py:45 +msgid "Username same with user" +msgstr "" + +#: assets/models/_user.py:48 authentication/models/connection_token.py:34 +#: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 +#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 +#: terminal/serializers/storage.py:68 +msgid "Protocol" +msgstr "" + +#: assets/models/_user.py:49 +msgid "Auto push" +msgstr "" + +#: assets/models/_user.py:50 +msgid "Sudo" +msgstr "" + +#: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 +msgid "Shell" +msgstr "" + +#: assets/models/_user.py:52 +msgid "Login mode" +msgstr "" + +#: assets/models/_user.py:53 +msgid "SFTP Root" +msgstr "" + +#: assets/models/_user.py:54 +msgid "Home" +msgstr "" + +#: assets/models/_user.py:55 +msgid "System groups" +msgstr "" + +#: assets/models/_user.py:58 +msgid "User switch" +msgstr "" + +#: assets/models/_user.py:59 +msgid "Switch from" +msgstr "" + +#: assets/models/_user.py:65 audits/models.py:35 +#: xpack/plugins/change_auth_plan/models/app.py:35 +#: xpack/plugins/change_auth_plan/models/app.py:146 +msgid "System user" +msgstr "" + +#: assets/models/_user.py:67 +msgid "Can match system user" +msgstr "" + +#: assets/models/account.py:45 common/db/fields.py:232 +#: settings/serializers/terminal.py:14 +msgid "All" +msgstr "" + +#: assets/models/account.py:46 +msgid "Manual input" +msgstr "" + +#: assets/models/account.py:47 +msgid "Dynamic user" +msgstr "" + +#: assets/models/account.py:55 +#: authentication/serializers/connect_token_secret.py:47 +msgid "Su from" +msgstr "" + +#: assets/models/account.py:57 settings/serializers/auth/cas.py:20 +#: terminal/models/applet/applet.py:22 +msgid "Version" +msgstr "" + +#: assets/models/account.py:67 +msgid "Can view asset account secret" +msgstr "" + +#: assets/models/account.py:68 +msgid "Can change asset account secret" +msgstr "" + +#: assets/models/account.py:69 +msgid "Can view asset history account" +msgstr "" + +#: assets/models/account.py:70 +msgid "Can view asset history account secret" +msgstr "" + +#: assets/models/account.py:93 assets/serializers/account/account.py:15 +msgid "Account template" +msgstr "" + +#: assets/models/account.py:98 +msgid "Can view asset account template secret" +msgstr "" + +#: assets/models/account.py:99 +msgid "Can change asset account template secret" +msgstr "" + +#: assets/models/asset/common.py:93 assets/models/platform.py:110 +#: assets/serializers/asset/common.py:65 +#: perms/serializers/user_permission.py:21 +#: xpack/plugins/cloud/serializers/account_attrs.py:179 +msgid "Platform" +msgstr "" + +#: assets/models/asset/common.py:95 assets/models/domain.py:26 +#: assets/serializers/asset/common.py:64 +#: authentication/serializers/connect_token_secret.py:105 +msgid "Domain" +msgstr "" + +#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 +#: assets/models/cmd_filter.py:37 assets/serializers/asset/common.py:66 +#: assets/serializers/automations/base.py:21 +#: perms/models/asset_permission.py:62 +#: xpack/plugins/change_auth_plan/models/asset.py:44 +#: xpack/plugins/gathered_user/models.py:24 +msgid "Nodes" +msgstr "" + +#: assets/models/asset/common.py:98 assets/models/automations/base.py:21 +#: assets/models/base.py:71 assets/models/cmd_filter.py:44 +#: assets/models/label.py:21 terminal/models/applet/applet.py:25 +#: users/serializers/user.py:202 +msgid "Is active" +msgstr "" + +#: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 +msgid "Labels" +msgstr "" + +#: assets/models/asset/common.py:215 +msgid "Can refresh asset hardware info" +msgstr "" + +#: assets/models/asset/common.py:216 +msgid "Can test asset connectivity" +msgstr "" + +#: assets/models/asset/common.py:217 +msgid "Can push account to asset" +msgstr "" + +#: assets/models/asset/common.py:218 +msgid "Can match asset" +msgstr "" + +#: assets/models/asset/common.py:219 +msgid "Add asset to node" +msgstr "" + +#: assets/models/asset/common.py:220 +msgid "Move asset to node" +msgstr "" + +#: assets/models/asset/database.py:9 settings/serializers/email.py:37 +msgid "Use SSL" +msgstr "" + +#: assets/models/asset/database.py:10 +msgid "CA cert" +msgstr "" + +#: assets/models/asset/database.py:11 +msgid "Client cert" +msgstr "" + +#: assets/models/asset/database.py:12 +msgid "Client key" +msgstr "" + +#: assets/models/asset/database.py:13 +msgid "Allow invalid cert" +msgstr "" + +#: assets/models/asset/web.py:9 audits/const.py:68 +#: terminal/serializers/applet_host.py:25 +msgid "Disabled" +msgstr "" + +#: assets/models/asset/web.py:10 settings/serializers/auth/base.py:10 +#: settings/serializers/basic.py:27 +msgid "Basic" +msgstr "" + +#: assets/models/asset/web.py:11 assets/models/asset/web.py:17 +msgid "Script" +msgstr "" + +#: assets/models/asset/web.py:13 +msgid "Autofill" +msgstr "" + +#: assets/models/asset/web.py:14 assets/serializers/platform.py:30 +msgid "Username selector" +msgstr "" + +#: assets/models/asset/web.py:15 assets/serializers/platform.py:33 +msgid "Password selector" +msgstr "" + +#: assets/models/asset/web.py:16 assets/serializers/platform.py:36 +msgid "Submit selector" +msgstr "" + +#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:43 +#: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 +#: perms/serializers/permission.py:32 rbac/tree.py:37 +msgid "Accounts" +msgstr "" + +#: assets/models/automations/base.py:19 +#: assets/serializers/automations/base.py:20 ops/models/base.py:17 +#: ops/models/job.py:44 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 +#: xpack/plugins/change_auth_plan/models/asset.py:40 +msgid "Assets" +msgstr "" + +#: assets/models/automations/base.py:82 assets/models/automations/base.py:89 +msgid "Automation task" +msgstr "" + +#: assets/models/automations/base.py:91 audits/models.py:129 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 +#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 +#: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 +#: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:20 +#: xpack/plugins/cloud/models.py:174 xpack/plugins/cloud/models.py:226 +msgid "Status" +msgstr "" + +#: assets/models/automations/base.py:93 assets/models/backup.py:76 +#: audits/models.py:41 ops/models/base.py:55 ops/models/celery.py:59 +#: ops/models/job.py:109 perms/models/asset_permission.py:67 +#: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 +#: tickets/models/ticket/apply_application.py:30 +#: tickets/models/ticket/apply_asset.py:19 +#: xpack/plugins/change_auth_plan/models/base.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:199 +#: xpack/plugins/gathered_user/models.py:71 +msgid "Date start" +msgstr "" + +#: assets/models/automations/base.py:94 +#: assets/models/automations/change_secret.py:59 ops/models/base.py:56 +#: ops/models/celery.py:60 ops/models/job.py:110 +#: terminal/models/applet/host.py:106 +msgid "Date finished" +msgstr "" + +#: assets/models/automations/base.py:96 +#: assets/serializers/automations/base.py:39 +msgid "Automation snapshot" +msgstr "" + +#: assets/models/automations/base.py:100 assets/models/backup.py:87 +#: assets/serializers/account/backup.py:37 +#: assets/serializers/automations/base.py:41 +#: xpack/plugins/change_auth_plan/models/base.py:121 +#: xpack/plugins/change_auth_plan/serializers/base.py:78 +msgid "Trigger mode" +msgstr "" + +#: assets/models/automations/base.py:104 +#: assets/serializers/automations/change_secret.py:103 +msgid "Automation task execution" +msgstr "" + +#: assets/models/automations/change_secret.py:15 assets/models/base.py:67 +#: assets/serializers/account/account.py:97 assets/serializers/base.py:13 +msgid "Secret type" +msgstr "" + +#: assets/models/automations/change_secret.py:19 +#: assets/serializers/automations/change_secret.py:25 +msgid "Secret strategy" +msgstr "" + +#: assets/models/automations/change_secret.py:21 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:69 +#: assets/serializers/base.py:16 authentication/models/temp_token.py:10 +#: authentication/templates/authentication/_access_key_modal.html:31 +#: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:19 +msgid "Secret" +msgstr "" + +#: assets/models/automations/change_secret.py:22 +#: xpack/plugins/change_auth_plan/models/base.py:39 +msgid "Password rules" +msgstr "" + +#: assets/models/automations/change_secret.py:25 +msgid "SSH key change strategy" +msgstr "" + +#: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 +#: assets/serializers/account/backup.py:30 +#: assets/serializers/automations/change_secret.py:40 +#: xpack/plugins/change_auth_plan/models/app.py:40 +#: xpack/plugins/change_auth_plan/models/asset.py:63 +#: xpack/plugins/change_auth_plan/serializers/base.py:45 +msgid "Recipient" +msgstr "" + +#: assets/models/automations/change_secret.py:34 +msgid "Change secret automation" +msgstr "" + +#: assets/models/automations/change_secret.py:56 +msgid "Old secret" +msgstr "" + +#: assets/models/automations/change_secret.py:58 +msgid "Date started" +msgstr "" + +#: assets/models/automations/change_secret.py:61 common/const/choices.py:20 +msgid "Error" +msgstr "" + +#: assets/models/automations/change_secret.py:64 +msgid "Change secret record" +msgstr "" + +#: assets/models/automations/discovery_account.py:8 +msgid "Discovery account automation" +msgstr "" + +#: assets/models/automations/gather_accounts.py:15 +#: assets/tasks/gather_accounts.py:28 +msgid "Gather asset accounts" +msgstr "" + +#: assets/models/automations/gather_facts.py:15 +msgid "Gather asset facts" +msgstr "" + +#: assets/models/automations/ping.py:15 +msgid "Ping asset" +msgstr "" + +#: assets/models/automations/push_account.py:16 +msgid "Push asset account" +msgstr "" + +#: assets/models/automations/verify_account.py:15 +msgid "Verify asset account" +msgstr "" + +#: assets/models/backup.py:37 assets/models/backup.py:95 +msgid "Account backup plan" +msgstr "" + +#: assets/models/backup.py:79 +#: authentication/templates/authentication/_msg_oauth_bind.html:11 +#: notifications/notifications.py:186 +#: xpack/plugins/change_auth_plan/models/base.py:111 +#: xpack/plugins/change_auth_plan/models/base.py:200 +#: xpack/plugins/gathered_user/models.py:74 +msgid "Time" +msgstr "" + +#: assets/models/backup.py:83 +msgid "Account backup snapshot" +msgstr "" + +#: assets/models/backup.py:90 audits/models.py:124 +#: terminal/models/session/sharing.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:197 +#: xpack/plugins/change_auth_plan/serializers/asset.py:171 +#: xpack/plugins/cloud/models.py:178 +msgid "Reason" +msgstr "" + +#: assets/models/backup.py:92 +#: assets/serializers/automations/change_secret.py:99 +#: assets/serializers/automations/change_secret.py:124 +#: terminal/serializers/session.py:36 +#: xpack/plugins/change_auth_plan/models/base.py:198 +#: xpack/plugins/change_auth_plan/serializers/asset.py:173 +msgid "Is success" +msgstr "" + +#: assets/models/backup.py:99 +msgid "Account backup execution" +msgstr "" + +#: assets/models/base.py:26 +msgid "Connectivity" +msgstr "" + +#: assets/models/base.py:28 authentication/models/temp_token.py:12 +msgid "Date verified" +msgstr "" + +#: assets/models/base.py:70 +msgid "Privileged" +msgstr "" + +#: assets/models/cmd_filter.py:33 perms/models/asset_permission.py:56 +#: users/models/group.py:31 users/models/user.py:681 +msgid "User group" +msgstr "" + +#: assets/models/cmd_filter.py:57 +msgid "Command filter" +msgstr "" + +#: assets/models/cmd_filter.py:71 +msgid "Deny" +msgstr "" + +#: assets/models/cmd_filter.py:72 +msgid "Allow" +msgstr "" + +#: assets/models/cmd_filter.py:73 +msgid "Reconfirm" +msgstr "" + +#: assets/models/cmd_filter.py:77 +msgid "Filter" +msgstr "" + +#: assets/models/cmd_filter.py:100 +msgid "Command filter rule" +msgstr "" + +#: assets/models/gateway.py:61 authentication/models/connection_token.py:101 +msgid "No account" +msgstr "" + +#: assets/models/gateway.py:83 +#, python-brace-format +msgid "Unable to connect to port {port} on {address}" +msgstr "" + +#: assets/models/gateway.py:86 authentication/middleware.py:76 +#: xpack/plugins/cloud/providers/fc.py:48 +msgid "Authentication failed" +msgstr "" + +#: assets/models/gateway.py:88 assets/models/gateway.py:115 +msgid "Connect failed" +msgstr "" + +#: assets/models/gathered_user.py:16 +msgid "Present" +msgstr "" + +#: assets/models/gathered_user.py:17 +msgid "Date last login" +msgstr "" + +#: assets/models/gathered_user.py:18 +msgid "IP last login" +msgstr "" + +#: assets/models/gathered_user.py:31 +msgid "GatherUser" +msgstr "" + +#: assets/models/group.py:30 +msgid "Asset group" +msgstr "" + +#: assets/models/group.py:34 assets/models/platform.py:19 +#: xpack/plugins/cloud/providers/nutanix.py:30 +msgid "Default" +msgstr "" + +#: assets/models/group.py:34 +msgid "Default asset group" +msgstr "" + +#: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:924 +msgid "System" +msgstr "" + +#: assets/models/label.py:18 assets/models/node.py:553 +#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 +#: authentication/models/connection_token.py:22 +#: common/drf/serializers/common.py:82 settings/models.py:34 +msgid "Value" +msgstr "" + +#: assets/models/label.py:36 assets/serializers/cagegory.py:6 +#: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 +#: settings/serializers/sms.py:7 +msgid "Label" +msgstr "" + +#: assets/models/node.py:158 +msgid "New node" +msgstr "" + +#: assets/models/node.py:481 +msgid "empty" +msgstr "" + +#: assets/models/node.py:552 perms/models/perm_node.py:21 +msgid "Key" +msgstr "" + +#: assets/models/node.py:554 assets/serializers/node.py:20 +msgid "Full value" +msgstr "" + +#: assets/models/node.py:558 perms/models/perm_node.py:22 +msgid "Parent key" +msgstr "" + +#: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 +#: xpack/plugins/cloud/serializers/task.py:74 +msgid "Node" +msgstr "" + +#: assets/models/node.py:570 +msgid "Can match node" +msgstr "" + +#: assets/models/platform.py:20 +msgid "Required" +msgstr "" + +#: assets/models/platform.py:23 settings/serializers/settings.py:61 +#: users/templates/users/reset_password.html:29 +msgid "Setting" +msgstr "" + +#: assets/models/platform.py:42 audits/const.py:69 settings/models.py:37 +#: terminal/serializers/applet_host.py:26 +msgid "Enabled" +msgstr "" + +#: assets/models/platform.py:43 +msgid "Ansible config" +msgstr "" + +#: assets/models/platform.py:44 +msgid "Ping enabled" +msgstr "" + +#: assets/models/platform.py:45 +msgid "Ping method" +msgstr "" + +#: assets/models/platform.py:46 assets/models/platform.py:56 +msgid "Gather facts enabled" +msgstr "" + +#: assets/models/platform.py:47 assets/models/platform.py:58 +msgid "Gather facts method" +msgstr "" + +#: assets/models/platform.py:48 +msgid "Push account enabled" +msgstr "" + +#: assets/models/platform.py:49 +msgid "Push account method" +msgstr "" + +#: assets/models/platform.py:50 +msgid "Change password enabled" +msgstr "" + +#: assets/models/platform.py:52 +msgid "Change password method" +msgstr "" + +#: assets/models/platform.py:53 +msgid "Verify account enabled" +msgstr "" + +#: assets/models/platform.py:55 +msgid "Verify account method" +msgstr "" + +#: assets/models/platform.py:75 tickets/models/ticket/general.py:299 +msgid "Meta" +msgstr "" + +#: assets/models/platform.py:76 +msgid "Internal" +msgstr "" + +#: assets/models/platform.py:80 assets/serializers/platform.py:97 +msgid "Charset" +msgstr "" + +#: assets/models/platform.py:82 +msgid "Domain enabled" +msgstr "" + +#: assets/models/platform.py:83 +msgid "Protocols enabled" +msgstr "" + +#: assets/models/platform.py:85 +msgid "Su enabled" +msgstr "" + +#: assets/models/platform.py:86 +msgid "SU method" +msgstr "" + +#: assets/models/platform.py:88 assets/serializers/platform.py:104 +msgid "Automation" +msgstr "" + +#: assets/models/utils.py:19 +#, python-format +msgid "%(value)s is not an even number" +msgstr "" + +#: assets/notifications.py:8 +msgid "Notification of account backup route task results" +msgstr "" + +#: assets/notifications.py:18 +msgid "" +"{} - The account backup passage task has been completed. See the attachment " +"for details" +msgstr "" + +#: assets/notifications.py:20 +msgid "" +"{} - The account backup passage task has been completed: the encryption " +"password has not been set - please go to personal information -> file " +"encryption password to set the encryption password" +msgstr "" + +#: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 +msgid "Notification of implementation result of encryption change plan" +msgstr "" + +#: assets/notifications.py:41 +#: xpack/plugins/change_auth_plan/notifications.py:18 +msgid "" +"{} - The encryption change task has been completed. See the attachment for " +"details" +msgstr "" + +#: assets/notifications.py:42 +#: xpack/plugins/change_auth_plan/notifications.py:19 +msgid "" +"{} - The encryption change task has been completed: the encryption password " +"has not been set - please go to personal information -> file encryption " +"password to set the encryption password" +msgstr "" + +#: assets/serializers/account/account.py:18 +msgid "Push now" +msgstr "" + +#: assets/serializers/account/account.py:20 +msgid "Has secret" +msgstr "" + +#: assets/serializers/account/account.py:27 +msgid "Account template not found" +msgstr "" + +#: assets/serializers/account/backup.py:29 +#: assets/serializers/automations/base.py:34 ops/mixin.py:22 ops/mixin.py:102 +#: settings/serializers/auth/ldap.py:66 +#: xpack/plugins/change_auth_plan/serializers/base.py:43 +msgid "Periodic perform" +msgstr "" + +#: assets/serializers/account/backup.py:31 +#: assets/serializers/automations/change_secret.py:41 +#: xpack/plugins/change_auth_plan/serializers/base.py:46 +msgid "Currently only mail sending is supported" +msgstr "" + +#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:102 +#: authentication/serializers/connect_token_secret.py:27 +#: authentication/serializers/connect_token_secret.py:63 +#: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 +#: xpack/plugins/cloud/serializers/task.py:43 +msgid "Protocols" +msgstr "" + +#: assets/serializers/asset/common.py:88 +msgid "Address" +msgstr "" + +#: assets/serializers/asset/common.py:156 +msgid "Platform not exist" +msgstr "" + +#: assets/serializers/asset/common.py:172 +msgid "Protocol is required: {}" +msgstr "" + +#: assets/serializers/asset/host.py:12 +msgid "Vendor" +msgstr "" + +#: assets/serializers/asset/host.py:13 +msgid "Model" +msgstr "" + +#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 +msgid "Serial number" +msgstr "" + +#: assets/serializers/asset/host.py:16 +msgid "CPU model" +msgstr "" + +#: assets/serializers/asset/host.py:17 +msgid "CPU count" +msgstr "" + +#: assets/serializers/asset/host.py:18 +msgid "CPU cores" +msgstr "" + +#: assets/serializers/asset/host.py:19 +msgid "CPU vcpus" +msgstr "" + +#: assets/serializers/asset/host.py:20 +msgid "Memory" +msgstr "" + +#: assets/serializers/asset/host.py:21 +msgid "Disk total" +msgstr "" + +#: assets/serializers/asset/host.py:22 +msgid "Disk info" +msgstr "" + +#: assets/serializers/asset/host.py:24 +msgid "OS" +msgstr "" + +#: assets/serializers/asset/host.py:25 +msgid "OS version" +msgstr "" + +#: assets/serializers/asset/host.py:26 +msgid "OS arch" +msgstr "" + +#: assets/serializers/asset/host.py:27 +msgid "Hostname raw" +msgstr "" + +#: assets/serializers/asset/host.py:28 +msgid "Asset number" +msgstr "" + +#: assets/serializers/automations/change_secret.py:28 +#: xpack/plugins/change_auth_plan/models/asset.py:50 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +msgid "SSH Key strategy" +msgstr "" + +#: assets/serializers/automations/change_secret.py:70 +#: xpack/plugins/change_auth_plan/serializers/base.py:58 +msgid "* Please enter the correct password length" +msgstr "" + +#: assets/serializers/automations/change_secret.py:73 +#: xpack/plugins/change_auth_plan/serializers/base.py:61 +msgid "* Password length range 6-30 bits" +msgstr "" + +#: assets/serializers/automations/change_secret.py:117 +#: assets/serializers/automations/change_secret.py:145 audits/const.py:74 +#: audits/models.py:40 common/const/choices.py:18 ops/serializers/celery.py:39 +#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 +#: xpack/plugins/change_auth_plan/serializers/asset.py:189 +msgid "Success" +msgstr "" + +#: assets/serializers/automations/gather_accounts.py:23 +msgid "Executed amount" +msgstr "" + +#: assets/serializers/base.py:21 +msgid "Key password" +msgstr "" + +#: assets/serializers/cagegory.py:9 +msgid "Constraints" +msgstr "" + +#: assets/serializers/cagegory.py:15 +msgid "Types" +msgstr "" + +#: assets/serializers/domain.py:16 +msgid "Gateway" +msgstr "" + +#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:9 +msgid "Hostname" +msgstr "" + +#: assets/serializers/label.py:12 +msgid "Assets amount" +msgstr "" + +#: assets/serializers/label.py:13 +msgid "Category display" +msgstr "" + +#: assets/serializers/node.py:17 +msgid "value" +msgstr "" + +#: assets/serializers/node.py:31 +msgid "Can't contains: /" +msgstr "" + +#: assets/serializers/node.py:41 +msgid "The same level node name cannot be the same" +msgstr "" + +#: assets/serializers/platform.py:24 +msgid "SFTP enabled" +msgstr "" + +#: assets/serializers/platform.py:25 +msgid "SFTP home" +msgstr "" + +#: assets/serializers/platform.py:28 +msgid "Auto fill" +msgstr "" + +#: assets/serializers/platform.py:79 +msgid "Primary" +msgstr "" + +#: assets/serializers/utils.py:13 +msgid "Password can not contains `{{` " +msgstr "" + +#: assets/serializers/utils.py:16 +msgid "Password can not contains `'` " +msgstr "" + +#: assets/serializers/utils.py:18 +msgid "Password can not contains `\"` " +msgstr "" + +#: assets/serializers/utils.py:24 +msgid "private key invalid or passphrase error" +msgstr "" + +#: assets/tasks/automation.py:11 +msgid "Execute automation" +msgstr "" + +#: assets/tasks/backup.py:13 +msgid "Execute account backup plan" +msgstr "" + +#: assets/tasks/gather_accounts.py:31 +msgid "Gather assets accounts" +msgstr "" + +#: assets/tasks/gather_facts.py:26 +msgid "Update some assets hardware info. " +msgstr "" + +#: assets/tasks/gather_facts.py:44 +msgid "Manually update the hardware information of assets" +msgstr "" + +#: assets/tasks/gather_facts.py:49 +msgid "Update assets hardware info: " +msgstr "" + +#: assets/tasks/gather_facts.py:53 +msgid "Manually update the hardware information of assets under a node" +msgstr "" + +#: assets/tasks/gather_facts.py:59 +msgid "Update node asset hardware information: " +msgstr "" + +#: assets/tasks/nodes_amount.py:16 +msgid "Check the amount of assets under the node" +msgstr "" + +#: assets/tasks/nodes_amount.py:28 +msgid "" +"The task of self-checking is already running and cannot be started repeatedly" +msgstr "" + +#: assets/tasks/nodes_amount.py:34 +msgid "Periodic check the amount of assets under the node" +msgstr "" + +#: assets/tasks/ping.py:21 assets/tasks/ping.py:39 +msgid "Test assets connectivity " +msgstr "" + +#: assets/tasks/ping.py:33 +msgid "Manually test the connectivity of a asset" +msgstr "" + +#: assets/tasks/ping.py:43 +msgid "Manually test the connectivity of assets under a node" +msgstr "" + +#: assets/tasks/ping.py:49 +msgid "Test if the assets under the node are connectable " +msgstr "" + +#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 +msgid "Push accounts to assets" +msgstr "" + +#: assets/tasks/utils.py:17 +msgid "Asset has been disabled, skipped: {}" +msgstr "" + +#: assets/tasks/utils.py:21 +msgid "Asset may not be support ansible, skipped: {}" +msgstr "" + +#: assets/tasks/utils.py:39 +msgid "For security, do not push user {}" +msgstr "" + +#: assets/tasks/utils.py:55 +msgid "No assets matched, stop task" +msgstr "" + +#: assets/tasks/verify_account.py:30 +msgid "Verify asset account availability" +msgstr "" + +#: assets/tasks/verify_account.py:37 +msgid "Verify accounts connectivity" +msgstr "" + +#: audits/apps.py:9 +msgid "Audits" +msgstr "" + +#: audits/backends/db.py:12 +msgid "The text content is too long. Use Elasticsearch to store operation logs" +msgstr "" + +#: audits/backends/db.py:24 audits/backends/db.py:26 +msgid "Tips" +msgstr "" + +#: audits/const.py:45 +msgid "Mkdir" +msgstr "" + +#: audits/const.py:46 +msgid "Rmdir" +msgstr "" + +#: audits/const.py:47 audits/const.py:57 +#: authentication/templates/authentication/_access_key_modal.html:65 +#: rbac/tree.py:226 +msgid "Delete" +msgstr "" + +#: audits/const.py:48 perms/const.py:13 +msgid "Upload" +msgstr "" + +#: audits/const.py:49 +msgid "Rename" +msgstr "" + +#: audits/const.py:50 +msgid "Symlink" +msgstr "" + +#: audits/const.py:51 perms/const.py:14 +msgid "Download" +msgstr "" + +#: audits/const.py:55 rbac/tree.py:224 +msgid "View" +msgstr "" + +#: audits/const.py:56 rbac/tree.py:225 templates/_csv_import_export.html:18 +#: templates/_csv_update_modal.html:6 +msgid "Update" +msgstr "" + +#: audits/const.py:58 +#: authentication/templates/authentication/_access_key_modal.html:22 +#: rbac/tree.py:223 +msgid "Create" +msgstr "" + +#: audits/const.py:63 settings/serializers/terminal.py:6 +#: terminal/models/applet/host.py:24 terminal/models/component/terminal.py:159 +msgid "Terminal" +msgstr "" + +#: audits/const.py:70 +msgid "-" +msgstr "" + +#: audits/handler.py:134 +msgid "Yes" +msgstr "" + +#: audits/handler.py:134 +msgid "No" +msgstr "" + +#: audits/models.py:32 audits/models.py:55 audits/models.py:96 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 +msgid "Remote addr" +msgstr "" + +#: audits/models.py:37 audits/serializers.py:19 +msgid "Operate" +msgstr "" + +#: audits/models.py:39 +msgid "Filename" +msgstr "" + +#: audits/models.py:44 +msgid "File transfer log" +msgstr "" + +#: audits/models.py:53 audits/serializers.py:91 +msgid "Resource Type" +msgstr "" + +#: audits/models.py:54 +msgid "Resource" +msgstr "" + +#: audits/models.py:56 audits/models.py:98 +#: terminal/backends/command/serializers.py:41 +msgid "Datetime" +msgstr "" + +#: audits/models.py:88 +msgid "Operate log" +msgstr "" + +#: audits/models.py:94 +msgid "Change by" +msgstr "" + +#: audits/models.py:104 +msgid "Password change log" +msgstr "" + +#: audits/models.py:111 +msgid "Login type" +msgstr "" + +#: audits/models.py:113 tickets/models/ticket/login_confirm.py:10 +msgid "Login ip" +msgstr "" + +#: audits/models.py:115 +#: authentication/templates/authentication/_msg_different_city.html:11 +#: tickets/models/ticket/login_confirm.py:11 +msgid "Login city" +msgstr "" + +#: audits/models.py:118 audits/serializers.py:62 +msgid "User agent" +msgstr "" + +#: audits/models.py:121 audits/serializers.py:39 +#: authentication/templates/authentication/_mfa_confirm_modal.html:14 +#: users/forms/profile.py:65 users/models/user.py:698 +#: users/serializers/profile.py:126 +msgid "MFA" +msgstr "" + +#: audits/models.py:131 +msgid "Date login" +msgstr "" + +#: audits/models.py:133 audits/serializers.py:64 +msgid "Authentication backend" +msgstr "" + +#: audits/models.py:174 +msgid "User login log" +msgstr "" + +#: audits/serializers.py:63 +msgid "Reason display" +msgstr "" + +#: audits/signal_handlers.py:48 +msgid "SSH Key" +msgstr "" + +#: audits/signal_handlers.py:50 settings/serializers/auth/sso.py:10 +msgid "SSO" +msgstr "" + +#: audits/signal_handlers.py:51 +msgid "Auth Token" +msgstr "" + +#: audits/signal_handlers.py:52 authentication/notifications.py:73 +#: authentication/views/login.py:73 authentication/views/wecom.py:178 +#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 +#: users/models/user.py:736 +msgid "WeCom" +msgstr "" + +#: audits/signal_handlers.py:53 authentication/views/feishu.py:145 +#: authentication/views/login.py:85 notifications/backends/__init__.py:14 +#: settings/serializers/auth/feishu.py:10 users/models/user.py:738 +msgid "FeiShu" +msgstr "" + +#: audits/signal_handlers.py:54 authentication/views/dingtalk.py:180 +#: authentication/views/login.py:79 notifications/backends/__init__.py:12 +#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:737 +msgid "DingTalk" +msgstr "" + +#: audits/signal_handlers.py:55 authentication/models/temp_token.py:16 +msgid "Temporary token" +msgstr "" + +#: authentication/api/confirm.py:40 +msgid "This action require verify your MFA" +msgstr "" + +#: authentication/api/mfa.py:59 +msgid "Current user not support mfa type: {}" +msgstr "" + +#: authentication/api/password.py:31 terminal/api/session/session.py:225 +#: users/views/profile/reset.py:44 +msgid "User does not exist: {}" +msgstr "" + +#: authentication/api/password.py:31 users/views/profile/reset.py:127 +msgid "No user matched" +msgstr "" + +#: authentication/api/password.py:35 +msgid "" +"The user is from {}, please go to the corresponding system to change the " +"password" +msgstr "" + +#: authentication/api/password.py:59 +#: authentication/templates/authentication/login.html:256 +#: users/templates/users/forgot_password.html:27 +#: users/templates/users/forgot_password.html:28 +#: users/templates/users/forgot_password_previewing.html:13 +#: users/templates/users/forgot_password_previewing.html:14 +msgid "Forgot password" +msgstr "" + +#: authentication/apps.py:7 settings/serializers/auth/base.py:10 +#: settings/serializers/auth/cas.py:10 settings/serializers/auth/dingtalk.py:10 +#: settings/serializers/auth/feishu.py:10 settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/oauth2.py:19 settings/serializers/auth/oidc.py:12 +#: settings/serializers/auth/radius.py:13 settings/serializers/auth/saml2.py:11 +#: settings/serializers/auth/sso.py:10 settings/serializers/auth/wecom.py:10 +msgid "Authentication" +msgstr "" + +#: authentication/backends/custom.py:58 +#: authentication/backends/oauth2/backends.py:158 +msgid "User invalid, disabled or expired" +msgstr "" + +#: authentication/backends/drf.py:56 +msgid "Invalid signature header. No credentials provided." +msgstr "" + +#: authentication/backends/drf.py:59 +msgid "Invalid signature header. Signature string should not contain spaces." +msgstr "" + +#: authentication/backends/drf.py:66 +msgid "Invalid signature header. Format like AccessKeyId:Signature" +msgstr "" + +#: authentication/backends/drf.py:70 +msgid "" +"Invalid signature header. Signature string should not contain invalid " +"characters." +msgstr "" + +#: authentication/backends/drf.py:90 authentication/backends/drf.py:106 +msgid "Invalid signature." +msgstr "" + +#: authentication/backends/drf.py:97 +msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" +msgstr "" + +#: authentication/backends/drf.py:102 +msgid "Expired, more than 15 minutes" +msgstr "" + +#: authentication/backends/drf.py:109 +msgid "User disabled." +msgstr "" + +#: authentication/backends/drf.py:127 +msgid "Invalid token header. No credentials provided." +msgstr "" + +#: authentication/backends/drf.py:130 +msgid "Invalid token header. Sign string should not contain spaces." +msgstr "" + +#: authentication/backends/drf.py:137 +msgid "" +"Invalid token header. Sign string should not contain invalid characters." +msgstr "" + +#: authentication/backends/drf.py:148 +msgid "Invalid token or cache refreshed." +msgstr "" + +#: authentication/confirm/password.py:16 +msgid "Authentication failed password incorrect" +msgstr "" + +#: authentication/confirm/relogin.py:10 +msgid "Login time has exceeded {} minutes, please login again" +msgstr "" + +#: authentication/errors/const.py:18 +msgid "Username/password check failed" +msgstr "" + +#: authentication/errors/const.py:19 +msgid "Password decrypt failed" +msgstr "" + +#: authentication/errors/const.py:20 +msgid "MFA failed" +msgstr "" + +#: authentication/errors/const.py:21 +msgid "MFA unset" +msgstr "" + +#: authentication/errors/const.py:22 +msgid "Username does not exist" +msgstr "" + +#: authentication/errors/const.py:23 +msgid "Password expired" +msgstr "" + +#: authentication/errors/const.py:24 +msgid "Disabled or expired" +msgstr "" + +#: authentication/errors/const.py:25 +msgid "This account is inactive." +msgstr "" + +#: authentication/errors/const.py:26 +msgid "This account is expired" +msgstr "" + +#: authentication/errors/const.py:27 +msgid "Auth backend not match" +msgstr "" + +#: authentication/errors/const.py:28 +msgid "ACL is not allowed" +msgstr "" + +#: authentication/errors/const.py:29 +msgid "Only local users are allowed" +msgstr "" + +#: authentication/errors/const.py:39 +msgid "No session found, check your cookie" +msgstr "" + +#: authentication/errors/const.py:41 +#, python-brace-format +msgid "" +"The username or password you entered is incorrect, please enter it again. " +"You can also try {times_try} times (The account will be temporarily locked " +"for {block_time} minutes)" +msgstr "" + +#: authentication/errors/const.py:47 authentication/errors/const.py:55 +msgid "" +"The account has been locked (please contact admin to unlock it or try again " +"after {} minutes)" +msgstr "" + +#: authentication/errors/const.py:51 +msgid "" +"The address has been locked (please contact admin to unlock it or try again " +"after {} minutes)" +msgstr "" + +#: authentication/errors/const.py:59 +#, python-brace-format +msgid "" +"{error}, You can also try {times_try} times (The account will be temporarily " +"locked for {block_time} minutes)" +msgstr "" + +#: authentication/errors/const.py:63 +msgid "MFA required" +msgstr "" + +#: authentication/errors/const.py:64 +msgid "MFA not set, please set it first" +msgstr "" + +#: authentication/errors/const.py:65 +msgid "Login confirm required" +msgstr "" + +#: authentication/errors/const.py:66 +msgid "Wait login confirm ticket for accept" +msgstr "" + +#: authentication/errors/const.py:67 +msgid "Login confirm ticket was {}" +msgstr "" + +#: authentication/errors/failed.py:146 +msgid "Current IP and Time period is not allowed" +msgstr "" + +#: authentication/errors/failed.py:151 +msgid "Please enter MFA code" +msgstr "" + +#: authentication/errors/failed.py:156 +msgid "Please enter SMS code" +msgstr "" + +#: authentication/errors/failed.py:161 users/exceptions.py:15 +msgid "Phone not set" +msgstr "" + +#: authentication/errors/mfa.py:8 +msgid "SSO auth closed" +msgstr "" + +#: authentication/errors/mfa.py:18 authentication/views/wecom.py:80 +msgid "WeCom is already bound" +msgstr "" + +#: authentication/errors/mfa.py:23 authentication/views/wecom.py:237 +#: authentication/views/wecom.py:291 +msgid "WeCom is not bound" +msgstr "" + +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 +#: authentication/views/dingtalk.py:297 +msgid "DingTalk is not bound" +msgstr "" + +#: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 +msgid "FeiShu is not bound" +msgstr "" + +#: authentication/errors/mfa.py:38 +msgid "Your password is invalid" +msgstr "" + +#: authentication/errors/redirect.py:85 authentication/mixins.py:306 +msgid "Your password is too simple, please change it for security" +msgstr "" + +#: authentication/errors/redirect.py:93 authentication/mixins.py:313 +msgid "You should to change your password before login" +msgstr "" + +#: authentication/errors/redirect.py:101 authentication/mixins.py:320 +msgid "Your password has expired, please reset before logging in" +msgstr "" + +#: authentication/forms.py:45 +msgid "{} days auto login" +msgstr "" + +#: authentication/forms.py:56 +msgid "MFA Code" +msgstr "" + +#: authentication/forms.py:57 +msgid "MFA type" +msgstr "" + +#: authentication/forms.py:65 +#: authentication/templates/authentication/_captcha_field.html:15 +msgid "Captcha" +msgstr "" + +#: authentication/forms.py:70 users/forms/profile.py:28 +msgid "MFA code" +msgstr "" + +#: authentication/forms.py:72 +msgid "Dynamic code" +msgstr "" + +#: authentication/mfa/base.py:7 +msgid "Please input security code" +msgstr "" + +#: authentication/mfa/custom.py:20 +msgid "MFA Custom code invalid" +msgstr "" + +#: authentication/mfa/custom.py:26 +msgid "MFA custom verification code" +msgstr "" + +#: authentication/mfa/custom.py:56 +msgid "MFA custom global enabled, cannot disable" +msgstr "" + +#: authentication/mfa/otp.py:7 +msgid "OTP code invalid, or server time error" +msgstr "" + +#: authentication/mfa/otp.py:12 +msgid "OTP" +msgstr "" + +#: authentication/mfa/otp.py:13 +msgid "OTP verification code" +msgstr "" + +#: authentication/mfa/otp.py:48 +msgid "Virtual OTP based MFA" +msgstr "" + +#: authentication/mfa/radius.py:7 +msgid "Radius verify code invalid" +msgstr "" + +#: authentication/mfa/radius.py:13 +msgid "Radius verification code" +msgstr "" + +#: authentication/mfa/radius.py:44 +msgid "Radius global enabled, cannot disable" +msgstr "" + +#: authentication/mfa/sms.py:7 +msgid "SMS verify code invalid" +msgstr "" + +#: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:16 +#: authentication/serializers/password_mfa.py:24 +#: settings/serializers/auth/sms.py:27 users/forms/profile.py:103 +#: users/forms/profile.py:106 users/templates/users/forgot_password.html:111 +#: users/views/profile/reset.py:79 +msgid "SMS" +msgstr "" + +#: authentication/mfa/sms.py:13 +msgid "SMS verification code" +msgstr "" + +#: authentication/mfa/sms.py:57 +msgid "Set phone number to enable" +msgstr "" + +#: authentication/mfa/sms.py:61 +msgid "Clear phone number to disable" +msgstr "" + +#: authentication/middleware.py:77 settings/utils/ldap.py:652 +msgid "Authentication failed (before login check failed): {}" +msgstr "" + +#: authentication/mixins.py:256 +msgid "The MFA type ({}) is not enabled" +msgstr "" + +#: authentication/mixins.py:296 +msgid "Please change your password" +msgstr "" + +#: authentication/models/connection_token.py:31 +#: terminal/serializers/storage.py:111 +msgid "Account name" +msgstr "" + +#: authentication/models/connection_token.py:32 +msgid "Input username" +msgstr "" + +#: authentication/models/connection_token.py:33 +msgid "Input secret" +msgstr "" + +#: authentication/models/connection_token.py:35 +#: authentication/serializers/connect_token_secret.py:110 +#: perms/models/perm_token.py:17 +msgid "Connect method" +msgstr "" + +#: authentication/models/connection_token.py:36 +#: rbac/serializers/rolebinding.py:21 +msgid "User display" +msgstr "" + +#: authentication/models/connection_token.py:37 +msgid "Asset display" +msgstr "" + +#: authentication/models/connection_token.py:38 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 +#: tickets/models/ticket/apply_application.py:31 +#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:719 +msgid "Date expired" +msgstr "" + +#: authentication/models/connection_token.py:42 +msgid "Connection token" +msgstr "" + +#: authentication/models/connection_token.py:44 +msgid "Can view connection token secret" +msgstr "" + +#: authentication/models/connection_token.py:91 +msgid "Connection token expired at: {}" +msgstr "" + +#: authentication/models/connection_token.py:94 +msgid "No user or invalid user" +msgstr "" + +#: authentication/models/connection_token.py:98 +msgid "No asset or inactive asset" +msgstr "" + +#: authentication/models/connection_token.py:173 +msgid "Super connection token" +msgstr "" + +#: authentication/models/private_token.py:9 +msgid "Private Token" +msgstr "" + +#: authentication/models/sso_token.py:15 +msgid "Expired" +msgstr "" + +#: authentication/models/sso_token.py:20 +msgid "SSO token" +msgstr "" + +#: authentication/models/temp_token.py:11 +msgid "Verified" +msgstr "" + +#: authentication/notifications.py:19 +msgid "Different city login reminder" +msgstr "" + +#: authentication/notifications.py:52 +msgid "binding reminder" +msgstr "" + +#: authentication/serializers/connect_token_secret.py:109 +msgid "Expired now" +msgstr "" + +#: authentication/serializers/connection_token.py:14 +msgid "Expired time" +msgstr "" + +#: authentication/serializers/password_mfa.py:16 +#: authentication/serializers/password_mfa.py:24 +#: notifications/backends/__init__.py:10 settings/serializers/email.py:19 +#: settings/serializers/email.py:50 users/forms/profile.py:102 +#: users/forms/profile.py:106 users/models/user.py:677 +#: users/templates/users/forgot_password.html:116 +#: users/views/profile/reset.py:73 +msgid "Email" +msgstr "" + +#: authentication/serializers/password_mfa.py:29 +#: users/templates/users/forgot_password.html:107 +msgid "The {} cannot be empty" +msgstr "" + +#: authentication/serializers/token.py:79 perms/serializers/permission.py:30 +#: perms/serializers/permission.py:61 users/serializers/user.py:203 +msgid "Is valid" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:6 +msgid "API key list" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:18 +msgid "Using api key sign api header, every requests header difference" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:19 +msgid "docs" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:30 +#: users/serializers/group.py:35 +msgid "ID" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:33 +#: terminal/notifications.py:96 terminal/notifications.py:144 +msgid "Date" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:48 +msgid "Show" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:66 +#: settings/serializers/security.py:39 users/models/user.py:559 +#: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 +#: users/templates/users/user_verify_mfa.html:36 +msgid "Disable" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:67 +#: users/models/user.py:560 users/serializers/profile.py:117 +#: users/templates/users/mfa_setting.html:26 +#: users/templates/users/mfa_setting.html:68 +msgid "Enable" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:147 +msgid "Delete success" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:155 +#: authentication/templates/authentication/_mfa_confirm_modal.html:53 +#: templates/_modal.html:22 tickets/const.py:44 +msgid "Close" +msgstr "" + +#: authentication/templates/authentication/_captcha_field.html:8 +msgid "Play CAPTCHA as audio file" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:5 +msgid "MFA confirm" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:17 +msgid "Need MFA for view auth" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:20 +#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:37 +#: templates/_modal.html:23 templates/flash_message_standalone.html:37 +#: users/templates/users/user_password_verify.html:20 +msgid "Confirm" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:25 +msgid "Code error" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:3 +#: authentication/templates/authentication/_msg_oauth_bind.html:3 +#: authentication/templates/authentication/_msg_reset_password.html:3 +#: authentication/templates/authentication/_msg_reset_password_code.html:9 +#: authentication/templates/authentication/_msg_rest_password_success.html:2 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 +#: jumpserver/conf.py:414 +#: perms/templates/perms/_msg_item_permissions_expire.html:3 +#: perms/templates/perms/_msg_permed_items_expire.html:3 +#: tickets/templates/tickets/approve_check_password.html:33 +#: users/templates/users/_msg_account_expire_reminder.html:4 +#: users/templates/users/_msg_password_expire_reminder.html:4 +#: users/templates/users/_msg_reset_mfa.html:4 +#: users/templates/users/_msg_reset_ssh_key.html:4 +msgid "Hello" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:6 +msgid "Your account has remote login behavior, please pay attention" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:10 +msgid "Login time" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:16 +msgid "" +"If you suspect that the login behavior is abnormal, please modify the " +"account password in time." +msgstr "" + +#: authentication/templates/authentication/_msg_oauth_bind.html:6 +msgid "Your account has just been bound to" +msgstr "" + +#: authentication/templates/authentication/_msg_oauth_bind.html:17 +msgid "If the operation is not your own, unbind and change the password." +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:6 +msgid "" +"Please click the link below to reset your password, if not your request, " +"concern your account security" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:10 +msgid "Click here reset password" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:16 +#: users/templates/users/_msg_user_created.html:22 +msgid "This link is valid for 1 hour. After it expires" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:17 +#: users/templates/users/_msg_user_created.html:23 +msgid "request new one" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password_code.html:12 +#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 +#: users/forms/profile.py:104 users/templates/users/forgot_password.html:65 +msgid "Verify code" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password_code.html:15 +msgid "" +"Copy the verification code to the Reset Password page to reset the password." +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password_code.html:18 +msgid "The validity period of the verification code is one minute" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:5 +msgid "Your password has just been successfully updated" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:9 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:9 +msgid "Browser" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:13 +msgid "" +"If the password update was not initiated by you, your account may have " +"security issues" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:14 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:14 +msgid "If you have any questions, you can contact the administrator" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_public_key_success.html:5 +msgid "Your public key has just been successfully updated" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_public_key_success.html:13 +msgid "" +"If the public key update was not initiated by you, your account may have " +"security issues" +msgstr "" + +#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 +#: templates/flash_message_standalone.html:28 tickets/const.py:17 +msgid "Cancel" +msgstr "" + +#: authentication/templates/authentication/login.html:221 +msgid "Welcome back, please enter username and password to login" +msgstr "" + +#: authentication/templates/authentication/login.html:264 +#: templates/_header_bar.html:89 +msgid "Login" +msgstr "" + +#: authentication/templates/authentication/login.html:271 +msgid "More login options" +msgstr "" + +#: authentication/templates/authentication/login_mfa.html:6 +msgid "MFA Auth" +msgstr "" + +#: authentication/templates/authentication/login_mfa.html:19 +#: users/templates/users/user_otp_check_password.html:12 +#: users/templates/users/user_otp_enable_bind.html:24 +#: users/templates/users/user_otp_enable_install_app.html:29 +#: users/templates/users/user_verify_mfa.html:30 +msgid "Next" +msgstr "" + +#: authentication/templates/authentication/login_mfa.html:22 +msgid "Can't provide security? Please contact the administrator!" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:41 +msgid "Refresh" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:46 +msgid "Copy link" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:51 +msgid "Return" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:116 +msgid "Copy success" +msgstr "" + +#: authentication/utils.py:28 common/utils/ip/geoip/utils.py:24 +#: xpack/plugins/cloud/const.py:27 +msgid "LAN" +msgstr "" + +#: authentication/views/dingtalk.py:42 +msgid "DingTalk Error, Please contact your system administrator" +msgstr "" + +#: authentication/views/dingtalk.py:45 +msgid "DingTalk Error" +msgstr "" + +#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 +#: authentication/views/wecom.py:56 +msgid "" +"The system configuration is incorrect. Please contact your administrator" +msgstr "" + +#: authentication/views/dingtalk.py:81 +msgid "DingTalk is already bound" +msgstr "" + +#: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 +msgid "Invalid user_id" +msgstr "" + +#: authentication/views/dingtalk.py:165 +msgid "DingTalk query user failed" +msgstr "" + +#: authentication/views/dingtalk.py:174 +msgid "The DingTalk is already bound to another user" +msgstr "" + +#: authentication/views/dingtalk.py:181 +msgid "Binding DingTalk successfully" +msgstr "" + +#: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 +msgid "Failed to get user from DingTalk" +msgstr "" + +#: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 +msgid "Please login with a password and then bind the DingTalk" +msgstr "" + +#: authentication/views/feishu.py:40 +msgid "FeiShu Error" +msgstr "" + +#: authentication/views/feishu.py:88 +msgid "FeiShu is already bound" +msgstr "" + +#: authentication/views/feishu.py:130 +msgid "FeiShu query user failed" +msgstr "" + +#: authentication/views/feishu.py:139 +msgid "The FeiShu is already bound to another user" +msgstr "" + +#: authentication/views/feishu.py:146 +msgid "Binding FeiShu successfully" +msgstr "" + +#: authentication/views/feishu.py:198 +msgid "Failed to get user from FeiShu" +msgstr "" + +#: authentication/views/feishu.py:205 +msgid "Please login with a password and then bind the FeiShu" +msgstr "" + +#: authentication/views/login.py:181 +msgid "Redirecting" +msgstr "" + +#: authentication/views/login.py:182 +msgid "Redirecting to {} authentication" +msgstr "" + +#: authentication/views/login.py:205 +msgid "Please enable cookies and try again." +msgstr "" + +#: authentication/views/login.py:307 +msgid "" +"Wait for {} confirm, You also can copy link to her/him
\n" +" Don't close this page" +msgstr "" + +#: authentication/views/login.py:312 +msgid "No ticket found" +msgstr "" + +#: authentication/views/login.py:348 +msgid "Logout success" +msgstr "" + +#: authentication/views/login.py:349 +msgid "Logout success, return login page" +msgstr "" + +#: authentication/views/wecom.py:41 +msgid "WeCom Error, Please contact your system administrator" +msgstr "" + +#: authentication/views/wecom.py:44 +msgid "WeCom Error" +msgstr "" + +#: authentication/views/wecom.py:163 +msgid "WeCom query user failed" +msgstr "" + +#: authentication/views/wecom.py:172 +msgid "The WeCom is already bound to another user" +msgstr "" + +#: authentication/views/wecom.py:179 +msgid "Binding WeCom successfully" +msgstr "" + +#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 +msgid "Failed to get user from WeCom" +msgstr "" + +#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 +msgid "Please login with a password and then bind the WeCom" +msgstr "" + +#: common/const/__init__.py:6 +#, python-format +msgid "%(name)s was created successfully" +msgstr "" + +#: common/const/__init__.py:7 +#, python-format +msgid "%(name)s was updated successfully" +msgstr "" + +#: common/const/choices.py:10 +msgid "Manual trigger" +msgstr "" + +#: common/const/choices.py:11 +msgid "Timing trigger" +msgstr "" + +#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 +msgid "Ready" +msgstr "" + +#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 +msgid "Pending" +msgstr "" + +#: common/const/choices.py:17 +msgid "Running" +msgstr "" + +#: common/const/choices.py:21 +msgid "Canceled" +msgstr "" + +#: common/db/encoder.py:11 +msgid "ugettext_lazy" +msgstr "" + +#: common/db/fields.py:94 +msgid "Marshal dict data to char field" +msgstr "" + +#: common/db/fields.py:98 +msgid "Marshal dict data to text field" +msgstr "" + +#: common/db/fields.py:110 +msgid "Marshal list data to char field" +msgstr "" + +#: common/db/fields.py:114 +msgid "Marshal list data to text field" +msgstr "" + +#: common/db/fields.py:118 +msgid "Marshal data to char field" +msgstr "" + +#: common/db/fields.py:122 +msgid "Marshal data to text field" +msgstr "" + +#: common/db/fields.py:164 +msgid "Encrypt field using Secret Key" +msgstr "" + +#: common/db/models.py:76 +msgid "Updated by" +msgstr "" + +#: common/drf/exc_handlers.py:25 +msgid "Object" +msgstr "" + +#: common/drf/fields.py:77 tickets/serializers/ticket/common.py:58 +#: xpack/plugins/change_auth_plan/serializers/asset.py:64 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:101 +#: xpack/plugins/cloud/serializers/account_attrs.py:56 +msgid "This field is required." +msgstr "" + +#: common/drf/fields.py:78 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "" + +#: common/drf/fields.py:79 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "" + +#: common/drf/fields.py:141 +msgid "Invalid data type, should be list" +msgstr "" + +#: common/drf/fields.py:156 +msgid "Invalid choice: {}" +msgstr "" + +#: common/drf/parsers/base.py:17 +msgid "The file content overflowed (The maximum length `{}` bytes)" +msgstr "" + +#: common/drf/parsers/base.py:159 +msgid "Parse file error: {}" +msgstr "" + +#: common/drf/serializers/common.py:86 +msgid "Children" +msgstr "" + +#: common/drf/serializers/common.py:94 +msgid "File" +msgstr "" + +#: common/exceptions.py:15 +#, python-format +msgid "%s object does not exist." +msgstr "" + +#: common/exceptions.py:25 +msgid "Someone else is doing this. Please wait for complete" +msgstr "" + +#: common/exceptions.py:30 +msgid "Your request timeout" +msgstr "" + +#: common/exceptions.py:35 +msgid "M2M reverse not allowed" +msgstr "" + +#: common/exceptions.py:41 +msgid "Is referenced by other objects and cannot be deleted" +msgstr "" + +#: common/exceptions.py:48 +msgid "This action require confirm current user" +msgstr "" + +#: common/exceptions.py:56 +msgid "Unexpect error occur" +msgstr "" + +#: common/mixins/api/action.py:52 +msgid "Request file format may be wrong" +msgstr "" + +#: common/mixins/models.py:33 +msgid "is discard" +msgstr "" + +#: common/mixins/models.py:34 +msgid "discard time" +msgstr "" + +#: common/mixins/views.py:58 +msgid "Export all" +msgstr "" + +#: common/mixins/views.py:60 +msgid "Export only selected items" +msgstr "" + +#: common/mixins/views.py:65 +#, python-format +msgid "Export filtered: %s" +msgstr "" + +#: common/plugins/es.py:28 +msgid "Invalid elasticsearch config" +msgstr "" + +#: common/plugins/es.py:33 +msgid "Not Support Elasticsearch8" +msgstr "" + +#: common/sdk/im/exceptions.py:23 +msgid "Network error, please contact system administrator" +msgstr "" + +#: common/sdk/im/wecom/__init__.py:15 +msgid "WeCom error, please contact system administrator" +msgstr "" + +#: common/sdk/sms/alibaba.py:56 +msgid "Signature does not match" +msgstr "" + +#: common/sdk/sms/cmpp2.py:46 +msgid "sp_id is 6 bits" +msgstr "" + +#: common/sdk/sms/cmpp2.py:216 +msgid "Failed to connect to the CMPP gateway server, err: {}" +msgstr "" + +#: common/sdk/sms/endpoint.py:16 +msgid "Alibaba cloud" +msgstr "" + +#: common/sdk/sms/endpoint.py:17 +msgid "Tencent cloud" +msgstr "" + +#: common/sdk/sms/endpoint.py:18 xpack/plugins/cloud/const.py:13 +msgid "Huawei Cloud" +msgstr "" + +#: common/sdk/sms/endpoint.py:19 +msgid "CMPP v2.0" +msgstr "" + +#: common/sdk/sms/endpoint.py:30 +msgid "SMS provider not support: {}" +msgstr "" + +#: common/sdk/sms/endpoint.py:51 +msgid "SMS verification code signature or template invalid" +msgstr "" + +#: common/sdk/sms/exceptions.py:8 +msgid "The verification code has expired. Please resend it" +msgstr "" + +#: common/sdk/sms/exceptions.py:13 +msgid "The verification code is incorrect" +msgstr "" + +#: common/sdk/sms/exceptions.py:18 +msgid "Please wait {} seconds before sending" +msgstr "" + +#: common/tasks.py:13 +msgid "Send email" +msgstr "" + +#: common/tasks.py:40 +msgid "Send email attachment" +msgstr "" + +#: common/utils/ip/geoip/utils.py:26 +msgid "Invalid ip" +msgstr "" + +#: common/utils/ip/utils.py:78 +msgid "Invalid address" +msgstr "" + +#: common/validators.py:14 +msgid "Special char not allowed" +msgstr "" + +#: common/validators.py:32 +msgid "This field must be unique." +msgstr "" + +#: common/validators.py:40 +msgid "Should not contains special characters" +msgstr "" + +#: common/validators.py:46 +msgid "The mobile phone number format is incorrect" +msgstr "" + +#: jumpserver/conf.py:413 +msgid "Create account successfully" +msgstr "" + +#: jumpserver/conf.py:415 +msgid "Your account has been created successfully" +msgstr "" + +#: jumpserver/context_processor.py:12 +msgid "JumpServer Open Source Bastion Host" +msgstr "" + +#: jumpserver/views/celery_flower.py:23 +msgid "

Flower service unavailable, check it

" +msgstr "" + +#: jumpserver/views/other.py:26 +msgid "" +"
Luna is a separately deployed program, you need to deploy Luna, koko, " +"configure nginx for url distribution,
If you see this page, " +"prove that you are not accessing the nginx listening port. Good luck." +msgstr "" + +#: jumpserver/views/other.py:70 +msgid "Websocket server run on port: {}, you should proxy it on nginx" +msgstr "" + +#: jumpserver/views/other.py:84 +msgid "" +"
Koko is a separately deployed program, you need to deploy Koko, " +"configure nginx for url distribution,
If you see this page, " +"prove that you are not accessing the nginx listening port. Good luck." +msgstr "" + +#: notifications/apps.py:7 +msgid "Notifications" +msgstr "" + +#: notifications/backends/__init__.py:13 +msgid "Site message" +msgstr "" + +#: notifications/models/notification.py:14 +msgid "receive backend" +msgstr "" + +#: notifications/models/notification.py:17 +msgid "User message" +msgstr "" + +#: notifications/models/notification.py:20 +msgid "{} subscription" +msgstr "" + +#: notifications/models/notification.py:32 +msgid "System message" +msgstr "" + +#: notifications/notifications.py:46 +msgid "Publish the station message" +msgstr "" + +#: ops/ansible/inventory.py:75 +msgid "No account available" +msgstr "" + +#: ops/ansible/inventory.py:178 +msgid "Ansible disabled" +msgstr "" + +#: ops/ansible/inventory.py:194 +msgid "Skip hosts below:" +msgstr "" + +#: ops/api/celery.py:63 ops/api/celery.py:78 +msgid "Waiting task start" +msgstr "" + +#: ops/apps.py:9 ops/notifications.py:16 +msgid "App ops" +msgstr "" + +#: ops/const.py:6 +msgid "Push" +msgstr "" + +#: ops/const.py:7 +msgid "Verify" +msgstr "" + +#: ops/const.py:8 +msgid "Collect" +msgstr "" + +#: ops/const.py:9 +msgid "Change password" +msgstr "" + +#: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 +msgid "Custom password" +msgstr "" + +#: ops/exception.py:6 +msgid "no valid program entry found." +msgstr "" + +#: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:73 +msgid "Cycle perform" +msgstr "" + +#: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 +#: settings/serializers/auth/ldap.py:70 +msgid "Regularly perform" +msgstr "" + +#: ops/mixin.py:108 +msgid "Interval" +msgstr "" + +#: ops/mixin.py:118 +msgid "* Please enter a valid crontab expression" +msgstr "" + +#: ops/mixin.py:125 +msgid "Range {} to {}" +msgstr "" + +#: ops/mixin.py:136 +msgid "Require periodic or regularly perform setting" +msgstr "" + +#: ops/models/adhoc.py:18 ops/models/job.py:31 +msgid "Powershell" +msgstr "" + +#: ops/models/adhoc.py:22 +msgid "Pattern" +msgstr "" + +#: ops/models/adhoc.py:24 ops/models/job.py:38 +msgid "Module" +msgstr "" + +#: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 +#: terminal/models/component/task.py:17 +msgid "Args" +msgstr "" + +#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 +#: terminal/models/session/sharing.py:24 +msgid "Creator" +msgstr "" + +#: ops/models/base.py:19 +msgid "Account policy" +msgstr "" + +#: ops/models/base.py:20 +msgid "Last execution" +msgstr "" + +#: ops/models/base.py:22 +msgid "Date last run" +msgstr "" + +#: ops/models/base.py:51 ops/models/job.py:105 +#: xpack/plugins/cloud/models.py:172 +msgid "Result" +msgstr "" + +#: ops/models/base.py:52 ops/models/job.py:106 +msgid "Summary" +msgstr "" + +#: ops/models/celery.py:55 terminal/models/component/task.py:18 +msgid "Kwargs" +msgstr "" + +#: ops/models/celery.py:56 tickets/models/comment.py:13 +#: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 +#: tickets/serializers/ticket/ticket.py:21 +msgid "State" +msgstr "" + +#: ops/models/celery.py:57 terminal/models/session/sharing.py:111 +#: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 +msgid "Finished" +msgstr "" + +#: ops/models/celery.py:58 +msgid "Date published" +msgstr "" + +#: ops/models/job.py:21 +msgid "Adhoc" +msgstr "" + +#: ops/models/job.py:22 ops/models/job.py:41 +msgid "Playbook" +msgstr "" + +#: ops/models/job.py:25 +msgid "Privileged Only" +msgstr "" + +#: ops/models/job.py:26 +msgid "Privileged First" +msgstr "" + +#: ops/models/job.py:27 +msgid "Skip" +msgstr "" + +#: ops/models/job.py:39 +msgid "Chdir" +msgstr "" + +#: ops/models/job.py:40 +msgid "Timeout (Seconds)" +msgstr "" + +#: ops/models/job.py:45 +msgid "Runas" +msgstr "" + +#: ops/models/job.py:47 +msgid "Runas policy" +msgstr "" + +#: ops/models/job.py:48 +msgid "Use Parameter Define" +msgstr "" + +#: ops/models/job.py:49 +msgid "Parameters define" +msgstr "" + +#: ops/models/job.py:104 +msgid "Parameters" +msgstr "" + +#: ops/notifications.py:17 +msgid "Server performance" +msgstr "" + +#: ops/notifications.py:23 +msgid "Terminal health check warning" +msgstr "" + +#: ops/notifications.py:68 +#, python-brace-format +msgid "The terminal is offline: {name}" +msgstr "" + +#: ops/notifications.py:73 +#, python-brace-format +msgid "Disk used more than {max_threshold}%: => {value}" +msgstr "" + +#: ops/notifications.py:78 +#, python-brace-format +msgid "Memory used more than {max_threshold}%: => {value}" +msgstr "" + +#: ops/notifications.py:83 +#, python-brace-format +msgid "CPU load more than {max_threshold}: => {value}" +msgstr "" + +#: ops/serializers/job.py:10 +msgid "Run after save" +msgstr "" + +#: ops/serializers/job.py:11 +msgid "Job type" +msgstr "" + +#: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 +#: terminal/models/component/task.py:26 +#: xpack/plugins/gathered_user/models.py:68 +msgid "Task" +msgstr "" + +#: ops/tasks.py:28 +msgid "Run ansible task" +msgstr "" + +#: ops/tasks.py:35 +msgid "Run ansible task execution" +msgstr "" + +#: ops/tasks.py:48 +msgid "Periodic clear celery tasks" +msgstr "" + +#: ops/tasks.py:50 +msgid "Clean celery log period" +msgstr "" + +#: ops/tasks.py:67 +msgid "Clear celery periodic tasks" +msgstr "" + +#: ops/tasks.py:90 +msgid "Create or update periodic tasks" +msgstr "" + +#: ops/tasks.py:98 +msgid "Periodic check service performance" +msgstr "" + +#: ops/templates/ops/celery_task_log.html:4 +msgid "Task log" +msgstr "" + +#: ops/utils.py:64 +msgid "Update task content: {}" +msgstr "" + +#: orgs/api.py:67 +msgid "The current organization ({}) cannot be deleted" +msgstr "" + +#: orgs/api.py:72 +msgid "" +"LDAP synchronization is set to the current organization. Please switch to " +"another organization before deleting" +msgstr "" + +#: orgs/api.py:81 +msgid "The organization have resource ({}) cannot be deleted" +msgstr "" + +#: orgs/apps.py:7 rbac/tree.py:113 +msgid "App organizations" +msgstr "" + +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 +#: rbac/const.py:7 rbac/models/rolebinding.py:48 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 +#: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:62 +msgid "Organization" +msgstr "" + +#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 +msgid "Org name" +msgstr "" + +#: orgs/models.py:72 +msgid "Builtin" +msgstr "" + +#: orgs/models.py:80 +msgid "GLOBAL" +msgstr "" + +#: orgs/models.py:82 +msgid "DEFAULT" +msgstr "" + +#: orgs/models.py:84 +msgid "SYSTEM" +msgstr "" + +#: orgs/models.py:90 +msgid "Can view root org" +msgstr "" + +#: orgs/models.py:91 +msgid "Can view all joined org" +msgstr "" + +#: orgs/tasks.py:9 +msgid "Refresh organization cache" +msgstr "" + +#: perms/apps.py:9 +msgid "App permissions" +msgstr "" + +#: perms/const.py:12 +msgid "Connect" +msgstr "" + +#: perms/const.py:15 +msgid "Copy" +msgstr "" + +#: perms/const.py:16 +msgid "Paste" +msgstr "" + +#: perms/const.py:26 +msgid "Transfer" +msgstr "" + +#: perms/const.py:27 +msgid "Clipboard" +msgstr "" + +#: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 +#: perms/serializers/permission.py:29 perms/serializers/permission.py:59 +#: tickets/models/ticket/apply_application.py:28 +#: tickets/models/ticket/apply_asset.py:18 +msgid "Actions" +msgstr "" + +#: perms/models/asset_permission.py:73 +msgid "From ticket" +msgstr "" + +#: perms/models/asset_permission.py:81 +msgid "Asset permission" +msgstr "" + +#: perms/models/perm_node.py:55 +msgid "Ungrouped" +msgstr "" + +#: perms/models/perm_node.py:57 +msgid "Favorite" +msgstr "" + +#: perms/models/perm_node.py:104 +msgid "Permed asset" +msgstr "" + +#: perms/models/perm_node.py:106 +msgid "Can view my assets" +msgstr "" + +#: perms/models/perm_node.py:107 +msgid "Can view user assets" +msgstr "" + +#: perms/models/perm_node.py:108 +msgid "Can view usergroup assets" +msgstr "" + +#: perms/models/perm_node.py:119 +msgid "Permed account" +msgstr "" + +#: perms/notifications.py:12 perms/notifications.py:44 +msgid "today" +msgstr "" + +#: perms/notifications.py:15 +msgid "You permed assets is about to expire" +msgstr "" + +#: perms/notifications.py:20 +msgid "permed assets" +msgstr "" + +#: perms/notifications.py:59 +msgid "Asset permissions is about to expire" +msgstr "" + +#: perms/notifications.py:64 +msgid "asset permissions of organization {}" +msgstr "" + +#: perms/serializers/permission.py:31 perms/serializers/permission.py:60 +#: users/serializers/user.py:100 users/serializers/user.py:205 +msgid "Is expired" +msgstr "" + +#: perms/templates/perms/_msg_item_permissions_expire.html:7 +#: perms/templates/perms/_msg_permed_items_expire.html:7 +#, python-format +msgid "" +"\n" +" The following %(item_type)s will expire in %(count)s days\n" +" " +msgstr "" + +#: perms/templates/perms/_msg_permed_items_expire.html:21 +msgid "If you have any question, please contact the administrator" +msgstr "" + +#: perms/utils/user_permission.py:627 rbac/tree.py:57 +msgid "My assets" +msgstr "" + +#: rbac/api/role.py:34 +msgid "Internal role, can't be destroy" +msgstr "" + +#: rbac/api/role.py:38 +msgid "The role has been bound to users, can't be destroy" +msgstr "" + +#: rbac/api/role.py:60 +msgid "Internal role, can't be update" +msgstr "" + +#: rbac/api/rolebinding.py:52 +msgid "{} at least one system role" +msgstr "" + +#: rbac/apps.py:7 +msgid "RBAC" +msgstr "" + +#: rbac/builtin.py:111 +msgid "SystemAdmin" +msgstr "" + +#: rbac/builtin.py:114 +msgid "SystemAuditor" +msgstr "" + +#: rbac/builtin.py:117 +msgid "SystemComponent" +msgstr "" + +#: rbac/builtin.py:123 +msgid "OrgAdmin" +msgstr "" + +#: rbac/builtin.py:126 +msgid "OrgAuditor" +msgstr "" + +#: rbac/builtin.py:129 +msgid "OrgUser" +msgstr "" + +#: rbac/models/menu.py:13 +msgid "Menu permission" +msgstr "" + +#: rbac/models/menu.py:15 +msgid "Can view console view" +msgstr "" + +#: rbac/models/menu.py:16 +msgid "Can view audit view" +msgstr "" + +#: rbac/models/menu.py:17 +msgid "Can view workbench view" +msgstr "" + +#: rbac/models/menu.py:18 +msgid "Can view web terminal" +msgstr "" + +#: rbac/models/menu.py:19 +msgid "Can view file manager" +msgstr "" + +#: rbac/models/permission.py:26 rbac/models/role.py:34 +msgid "Permissions" +msgstr "" + +#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 +#: settings/serializers/auth/oauth2.py:37 +msgid "Scope" +msgstr "" + +#: rbac/models/role.py:36 +msgid "Built-in" +msgstr "" + +#: rbac/models/role.py:46 rbac/models/rolebinding.py:44 +#: users/models/user.py:685 +msgid "Role" +msgstr "" + +#: rbac/models/role.py:144 +msgid "System role" +msgstr "" + +#: rbac/models/role.py:152 +msgid "Organization role" +msgstr "" + +#: rbac/models/rolebinding.py:53 +msgid "Role binding" +msgstr "" + +#: rbac/models/rolebinding.py:137 +msgid "All organizations" +msgstr "" + +#: rbac/models/rolebinding.py:166 +msgid "" +"User last role in org, can not be delete, you can remove user from org " +"instead" +msgstr "" + +#: rbac/models/rolebinding.py:173 +msgid "Organization role binding" +msgstr "" + +#: rbac/models/rolebinding.py:188 +msgid "System role binding" +msgstr "" + +#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 +msgid "Perms" +msgstr "" + +#: rbac/serializers/role.py:11 +msgid "Scope display" +msgstr "" + +#: rbac/serializers/role.py:26 users/serializers/group.py:34 +msgid "Users amount" +msgstr "" + +#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 +msgid "Display name" +msgstr "" + +#: rbac/serializers/rolebinding.py:22 +msgid "Role display" +msgstr "" + +#: rbac/serializers/rolebinding.py:56 +msgid "Has bound this role" +msgstr "" + +#: rbac/tree.py:18 rbac/tree.py:19 +msgid "All permissions" +msgstr "" + +#: rbac/tree.py:25 +msgid "Console view" +msgstr "" + +#: rbac/tree.py:26 +msgid "Workbench view" +msgstr "" + +#: rbac/tree.py:27 +msgid "Audit view" +msgstr "" + +#: rbac/tree.py:28 settings/models.py:156 +msgid "System setting" +msgstr "" + +#: rbac/tree.py:29 +msgid "Other" +msgstr "" + +#: rbac/tree.py:41 +msgid "Session audits" +msgstr "" + +#: rbac/tree.py:51 +msgid "Cloud import" +msgstr "" + +#: rbac/tree.py:52 +msgid "Backup account" +msgstr "" + +#: rbac/tree.py:53 +msgid "Gather account" +msgstr "" + +#: rbac/tree.py:54 +msgid "App change auth" +msgstr "" + +#: rbac/tree.py:55 +msgid "Asset change auth" +msgstr "" + +#: rbac/tree.py:56 +msgid "Terminal setting" +msgstr "" + +#: rbac/tree.py:58 +msgid "My apps" +msgstr "" + +#: rbac/tree.py:114 +msgid "Ticket comment" +msgstr "" + +#: rbac/tree.py:115 tickets/models/ticket/general.py:306 +msgid "Ticket" +msgstr "" + +#: rbac/tree.py:116 +msgid "Common setting" +msgstr "" + +#: rbac/tree.py:117 +msgid "View permission tree" +msgstr "" + +#: rbac/tree.py:118 +msgid "Execute batch command" +msgstr "" + +#: settings/api/dingtalk.py:31 settings/api/feishu.py:36 +#: settings/api/sms.py:148 settings/api/wecom.py:37 +msgid "Test success" +msgstr "" + +#: settings/api/email.py:20 +msgid "Test mail sent to {}, please check" +msgstr "" + +#: settings/api/ldap.py:166 +msgid "Synchronization start, please wait." +msgstr "" + +#: settings/api/ldap.py:170 +msgid "Synchronization is running, please wait." +msgstr "" + +#: settings/api/ldap.py:175 +msgid "Synchronization error: {}" +msgstr "" + +#: settings/api/ldap.py:213 +msgid "Get ldap users is None" +msgstr "" + +#: settings/api/ldap.py:222 +msgid "Imported {} users successfully (Organization: {})" +msgstr "" + +#: settings/api/sms.py:130 +msgid "Invalid SMS platform" +msgstr "" + +#: settings/api/sms.py:136 +msgid "test_phone is required" +msgstr "" + +#: settings/apps.py:7 +msgid "Settings" +msgstr "" + +#: settings/models.py:36 +msgid "Encrypted" +msgstr "" + +#: settings/models.py:158 +msgid "Can change email setting" +msgstr "" + +#: settings/models.py:159 +msgid "Can change auth setting" +msgstr "" + +#: settings/models.py:160 +msgid "Can change system msg sub setting" +msgstr "" + +#: settings/models.py:161 +msgid "Can change sms setting" +msgstr "" + +#: settings/models.py:162 +msgid "Can change security setting" +msgstr "" + +#: settings/models.py:163 +msgid "Can change clean setting" +msgstr "" + +#: settings/models.py:164 +msgid "Can change interface setting" +msgstr "" + +#: settings/models.py:165 +msgid "Can change license setting" +msgstr "" + +#: settings/models.py:166 +msgid "Can change terminal setting" +msgstr "" + +#: settings/models.py:167 +msgid "Can change other setting" +msgstr "" + +#: settings/serializers/auth/base.py:12 +msgid "CAS Auth" +msgstr "" + +#: settings/serializers/auth/base.py:13 +msgid "OPENID Auth" +msgstr "" + +#: settings/serializers/auth/base.py:14 +msgid "RADIUS Auth" +msgstr "" + +#: settings/serializers/auth/base.py:15 +msgid "DingTalk Auth" +msgstr "" + +#: settings/serializers/auth/base.py:16 +msgid "FeiShu Auth" +msgstr "" + +#: settings/serializers/auth/base.py:17 +msgid "WeCom Auth" +msgstr "" + +#: settings/serializers/auth/base.py:18 +msgid "SSO Auth" +msgstr "" + +#: settings/serializers/auth/base.py:19 +msgid "SAML2 Auth" +msgstr "" + +#: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 +msgid "Forgot password url" +msgstr "" + +#: settings/serializers/auth/base.py:28 +msgid "Enable login redirect msg" +msgstr "" + +#: settings/serializers/auth/cas.py:10 +msgid "CAS" +msgstr "" + +#: settings/serializers/auth/cas.py:12 +msgid "Enable CAS Auth" +msgstr "" + +#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 +msgid "Server url" +msgstr "" + +#: settings/serializers/auth/cas.py:16 +msgid "Proxy server url" +msgstr "" + +#: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:55 +#: settings/serializers/auth/saml2.py:34 +msgid "Logout completely" +msgstr "" + +#: settings/serializers/auth/cas.py:23 +msgid "Username attr" +msgstr "" + +#: settings/serializers/auth/cas.py:26 +msgid "Enable attributes map" +msgstr "" + +#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 +msgid "Rename attr" +msgstr "" + +#: settings/serializers/auth/cas.py:29 +msgid "Create user if not" +msgstr "" + +#: settings/serializers/auth/dingtalk.py:15 +msgid "Enable DingTalk Auth" +msgstr "" + +#: settings/serializers/auth/feishu.py:14 +msgid "Enable FeiShu Auth" +msgstr "" + +#: settings/serializers/auth/ldap.py:39 +msgid "LDAP" +msgstr "" + +#: settings/serializers/auth/ldap.py:42 +msgid "LDAP server" +msgstr "" + +#: settings/serializers/auth/ldap.py:43 +msgid "eg: ldap://localhost:389" +msgstr "" + +#: settings/serializers/auth/ldap.py:45 +msgid "Bind DN" +msgstr "" + +#: settings/serializers/auth/ldap.py:50 +msgid "User OU" +msgstr "" + +#: settings/serializers/auth/ldap.py:51 +msgid "Use | split multi OUs" +msgstr "" + +#: settings/serializers/auth/ldap.py:54 +msgid "User search filter" +msgstr "" + +#: settings/serializers/auth/ldap.py:55 +#, python-format +msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" +msgstr "" + +#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/oauth2.py:57 +#: settings/serializers/auth/oidc.py:37 +msgid "User attr map" +msgstr "" + +#: settings/serializers/auth/ldap.py:59 +msgid "" +"User attr map present how to map LDAP user attr to jumpserver, username,name," +"email is jumpserver attr" +msgstr "" + +#: settings/serializers/auth/ldap.py:77 +msgid "Connect timeout" +msgstr "" + +#: settings/serializers/auth/ldap.py:79 +msgid "Search paged size" +msgstr "" + +#: settings/serializers/auth/ldap.py:81 +msgid "Enable LDAP auth" +msgstr "" + +#: settings/serializers/auth/oauth2.py:19 +msgid "OAuth2" +msgstr "" + +#: settings/serializers/auth/oauth2.py:22 +msgid "Enable OAuth2 Auth" +msgstr "" + +#: settings/serializers/auth/oauth2.py:25 +msgid "Logo" +msgstr "" + +#: settings/serializers/auth/oauth2.py:28 +msgid "Service provider" +msgstr "" + +#: settings/serializers/auth/oauth2.py:31 settings/serializers/auth/oidc.py:19 +msgid "Client Id" +msgstr "" + +#: settings/serializers/auth/oauth2.py:34 settings/serializers/auth/oidc.py:22 +#: xpack/plugins/cloud/serializers/account_attrs.py:38 +msgid "Client Secret" +msgstr "" + +#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 +msgid "Provider auth endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 +msgid "Provider token endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:46 settings/serializers/auth/oidc.py:30 +msgid "Client authentication method" +msgstr "" + +#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 +msgid "Provider userinfo endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 +msgid "Provider end session endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 +#: settings/serializers/auth/saml2.py:35 +msgid "Always update user" +msgstr "" + +#: settings/serializers/auth/oidc.py:12 +msgid "OIDC" +msgstr "" + +#: settings/serializers/auth/oidc.py:16 +msgid "Base site url" +msgstr "" + +#: settings/serializers/auth/oidc.py:32 +msgid "Share session" +msgstr "" + +#: settings/serializers/auth/oidc.py:34 +msgid "Ignore ssl verification" +msgstr "" + +#: settings/serializers/auth/oidc.py:38 +msgid "" +"User attr map present how to map OpenID user attr to jumpserver, username," +"name,email is jumpserver attr" +msgstr "" + +#: settings/serializers/auth/oidc.py:46 +msgid "Use Keycloak" +msgstr "" + +#: settings/serializers/auth/oidc.py:52 +msgid "Realm name" +msgstr "" + +#: settings/serializers/auth/oidc.py:58 +msgid "Enable OPENID Auth" +msgstr "" + +#: settings/serializers/auth/oidc.py:60 +msgid "Provider endpoint" +msgstr "" + +#: settings/serializers/auth/oidc.py:69 +msgid "Provider jwks endpoint" +msgstr "" + +#: settings/serializers/auth/oidc.py:78 +msgid "Provider sign alg" +msgstr "" + +#: settings/serializers/auth/oidc.py:81 +msgid "Provider sign key" +msgstr "" + +#: settings/serializers/auth/oidc.py:83 +msgid "Scopes" +msgstr "" + +#: settings/serializers/auth/oidc.py:85 +msgid "Id token max age" +msgstr "" + +#: settings/serializers/auth/oidc.py:88 +msgid "Id token include claims" +msgstr "" + +#: settings/serializers/auth/oidc.py:90 +msgid "Use state" +msgstr "" + +#: settings/serializers/auth/oidc.py:91 +msgid "Use nonce" +msgstr "" + +#: settings/serializers/auth/radius.py:13 +msgid "Radius" +msgstr "" + +#: settings/serializers/auth/radius.py:15 +msgid "Enable Radius Auth" +msgstr "" + +#: settings/serializers/auth/radius.py:21 +msgid "OTP in Radius" +msgstr "" + +#: settings/serializers/auth/saml2.py:11 +msgid "SAML2" +msgstr "" + +#: settings/serializers/auth/saml2.py:14 +msgid "Enable SAML2 Auth" +msgstr "" + +#: settings/serializers/auth/saml2.py:17 +msgid "IDP metadata URL" +msgstr "" + +#: settings/serializers/auth/saml2.py:20 +msgid "IDP metadata XML" +msgstr "" + +#: settings/serializers/auth/saml2.py:23 +msgid "SP advanced settings" +msgstr "" + +#: settings/serializers/auth/saml2.py:27 +msgid "SP private key" +msgstr "" + +#: settings/serializers/auth/saml2.py:31 +msgid "SP cert" +msgstr "" + +#: settings/serializers/auth/sms.py:15 +msgid "Enable SMS" +msgstr "" + +#: settings/serializers/auth/sms.py:17 +msgid "SMS provider / Protocol" +msgstr "" + +#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:45 +#: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 +#: settings/serializers/auth/sms.py:73 settings/serializers/email.py:68 +msgid "Signature" +msgstr "" + +#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:46 +#: settings/serializers/auth/sms.py:54 settings/serializers/auth/sms.py:63 +msgid "Template code" +msgstr "" + +#: settings/serializers/auth/sms.py:31 +msgid "Test phone" +msgstr "" + +#: settings/serializers/auth/sms.py:60 +msgid "App Access Address" +msgstr "" + +#: settings/serializers/auth/sms.py:61 +msgid "Signature channel number" +msgstr "" + +#: settings/serializers/auth/sms.py:69 +msgid "Enterprise code(SP id)" +msgstr "" + +#: settings/serializers/auth/sms.py:70 +msgid "Shared secret(Shared secret)" +msgstr "" + +#: settings/serializers/auth/sms.py:71 +msgid "Original number(Src id)" +msgstr "" + +#: settings/serializers/auth/sms.py:72 +msgid "Business type(Service id)" +msgstr "" + +#: settings/serializers/auth/sms.py:75 +msgid "Template" +msgstr "" + +#: settings/serializers/auth/sms.py:76 +#, python-brace-format +msgid "" +"Template need contain {code} and Signature + template length does not exceed " +"67 words. For example, your verification code is {code}, which is valid for " +"5 minutes. Please do not disclose it to others." +msgstr "" + +#: settings/serializers/auth/sms.py:85 +#, python-brace-format +msgid "The template needs to contain {code}" +msgstr "" + +#: settings/serializers/auth/sms.py:88 +msgid "Signature + Template must not exceed 65 words" +msgstr "" + +#: settings/serializers/auth/sso.py:13 +msgid "Enable SSO auth" +msgstr "" + +#: settings/serializers/auth/sso.py:14 +msgid "Other service can using SSO token login to JumpServer without password" +msgstr "" + +#: settings/serializers/auth/sso.py:17 +msgid "SSO auth key TTL" +msgstr "" + +#: settings/serializers/auth/sso.py:17 +#: xpack/plugins/cloud/serializers/account_attrs.py:176 +msgid "Unit: second" +msgstr "" + +#: settings/serializers/auth/wecom.py:15 +msgid "Enable WeCom Auth" +msgstr "" + +#: settings/serializers/basic.py:9 +msgid "Subject" +msgstr "" + +#: settings/serializers/basic.py:13 +msgid "More url" +msgstr "" + +#: settings/serializers/basic.py:30 +msgid "Site url" +msgstr "" + +#: settings/serializers/basic.py:31 +msgid "eg: http://dev.jumpserver.org:8080" +msgstr "" + +#: settings/serializers/basic.py:34 +msgid "User guide url" +msgstr "" + +#: settings/serializers/basic.py:35 +msgid "User first login update profile done redirect to it" +msgstr "" + +#: settings/serializers/basic.py:39 +msgid "" +"The forgot password url on login page, If you use ldap or cas external " +"authentication, you can set it" +msgstr "" + +#: settings/serializers/basic.py:43 +msgid "Global organization name" +msgstr "" + +#: settings/serializers/basic.py:44 +msgid "The name of global organization to display" +msgstr "" + +#: settings/serializers/basic.py:46 +msgid "Enable announcement" +msgstr "" + +#: settings/serializers/basic.py:47 +msgid "Announcement" +msgstr "" + +#: settings/serializers/basic.py:48 +msgid "Enable tickets" +msgstr "" + +#: settings/serializers/cleaning.py:8 +msgid "Period clean" +msgstr "" + +#: settings/serializers/cleaning.py:12 +msgid "Login log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 +#: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 +#: settings/serializers/cleaning.py:28 +msgid "Unit: day" +msgstr "" + +#: settings/serializers/cleaning.py:16 +msgid "Task log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:20 +msgid "Operate log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:24 +msgid "FTP log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:28 +msgid "Cloud sync record keep days" +msgstr "" + +#: settings/serializers/cleaning.py:31 +msgid "Session keep duration" +msgstr "" + +#: settings/serializers/cleaning.py:32 +msgid "" +"Unit: days, Session, record, command will be delete if more than duration, " +"only in database" +msgstr "" + +#: settings/serializers/email.py:21 +msgid "SMTP host" +msgstr "" + +#: settings/serializers/email.py:22 +msgid "SMTP port" +msgstr "" + +#: settings/serializers/email.py:23 +msgid "SMTP account" +msgstr "" + +#: settings/serializers/email.py:25 +msgid "SMTP password" +msgstr "" + +#: settings/serializers/email.py:26 +msgid "Tips: Some provider use token except password" +msgstr "" + +#: settings/serializers/email.py:29 +msgid "Send user" +msgstr "" + +#: settings/serializers/email.py:30 +msgid "Tips: Send mail account, default SMTP account as the send account" +msgstr "" + +#: settings/serializers/email.py:33 +msgid "Test recipient" +msgstr "" + +#: settings/serializers/email.py:34 +msgid "Tips: Used only as a test mail recipient" +msgstr "" + +#: settings/serializers/email.py:38 +msgid "If SMTP port is 465, may be select" +msgstr "" + +#: settings/serializers/email.py:41 +msgid "Use TLS" +msgstr "" + +#: settings/serializers/email.py:42 +msgid "If SMTP port is 587, may be select" +msgstr "" + +#: settings/serializers/email.py:45 +msgid "Subject prefix" +msgstr "" + +#: settings/serializers/email.py:54 +msgid "Create user email subject" +msgstr "" + +#: settings/serializers/email.py:55 +msgid "" +"Tips: When creating a user, send the subject of the email (eg:Create account " +"successfully)" +msgstr "" + +#: settings/serializers/email.py:59 +msgid "Create user honorific" +msgstr "" + +#: settings/serializers/email.py:60 +msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" +msgstr "" + +#: settings/serializers/email.py:64 +msgid "Create user email content" +msgstr "" + +#: settings/serializers/email.py:65 +#, python-brace-format +msgid "" +"Tips: When creating a user, send the content of the email, support " +"{username} {name} {email} label" +msgstr "" + +#: settings/serializers/email.py:69 +msgid "Tips: Email signature (eg:jumpserver)" +msgstr "" + +#: settings/serializers/other.py:6 +msgid "More..." +msgstr "" + +#: settings/serializers/other.py:9 +msgid "Email suffix" +msgstr "" + +#: settings/serializers/other.py:10 +msgid "" +"This is used by default if no email is returned during SSO authentication" +msgstr "" + +#: settings/serializers/other.py:14 +msgid "OTP issuer name" +msgstr "" + +#: settings/serializers/other.py:18 +msgid "OTP valid window" +msgstr "" + +#: settings/serializers/other.py:23 +msgid "CMD" +msgstr "" + +#: settings/serializers/other.py:24 +msgid "PowerShell" +msgstr "" + +#: settings/serializers/other.py:26 +msgid "Shell (Windows)" +msgstr "" + +#: settings/serializers/other.py:27 +msgid "The shell type used when Windows assets perform ansible tasks" +msgstr "" + +#: settings/serializers/other.py:31 +msgid "Perm ungroup node" +msgstr "" + +#: settings/serializers/other.py:32 +msgid "Perm single to ungroup node" +msgstr "" + +#: settings/serializers/other.py:37 +msgid "Ticket authorize default time" +msgstr "" + +#: settings/serializers/other.py:40 +msgid "day" +msgstr "" + +#: settings/serializers/other.py:40 +msgid "hour" +msgstr "" + +#: settings/serializers/other.py:41 +msgid "Ticket authorize default time unit" +msgstr "" + +#: settings/serializers/other.py:44 +msgid "Help Docs URL" +msgstr "" + +#: settings/serializers/other.py:45 +msgid "default: http://docs.jumpserver.org" +msgstr "" + +#: settings/serializers/other.py:49 +msgid "Help Support URL" +msgstr "" + +#: settings/serializers/other.py:50 +msgid "default: http://www.jumpserver.org/support/" +msgstr "" + +#: settings/serializers/security.py:10 +msgid "Password minimum length" +msgstr "" + +#: settings/serializers/security.py:14 +msgid "Admin user password minimum length" +msgstr "" + +#: settings/serializers/security.py:17 +msgid "Must contain capital" +msgstr "" + +#: settings/serializers/security.py:20 +msgid "Must contain lowercase" +msgstr "" + +#: settings/serializers/security.py:23 +msgid "Must contain numeric" +msgstr "" + +#: settings/serializers/security.py:26 +msgid "Must contain special" +msgstr "" + +#: settings/serializers/security.py:31 +msgid "" +"Unit: minute, If the user has failed to log in for a limited number of " +"times, no login is allowed during this time interval." +msgstr "" + +#: settings/serializers/security.py:40 +msgid "All users" +msgstr "" + +#: settings/serializers/security.py:41 +msgid "Only admin users" +msgstr "" + +#: settings/serializers/security.py:43 +msgid "Global MFA auth" +msgstr "" + +#: settings/serializers/security.py:47 +msgid "Third-party login users perform MFA authentication" +msgstr "" + +#: settings/serializers/security.py:48 +msgid "The third-party login modes include OIDC, CAS, and SAML2" +msgstr "" + +#: settings/serializers/security.py:52 +msgid "Limit the number of user login failures" +msgstr "" + +#: settings/serializers/security.py:56 +msgid "Block user login interval" +msgstr "" + +#: settings/serializers/security.py:61 +msgid "Limit the number of IP login failures" +msgstr "" + +#: settings/serializers/security.py:65 +msgid "Block IP login interval" +msgstr "" + +#: settings/serializers/security.py:69 +msgid "Login IP White List" +msgstr "" + +#: settings/serializers/security.py:74 +msgid "Login IP Black List" +msgstr "" + +#: settings/serializers/security.py:80 +msgid "User password expiration" +msgstr "" + +#: settings/serializers/security.py:82 +msgid "" +"Unit: day, If the user does not update the password during the time, the " +"user password will expire failure;The password expiration reminder mail will " +"be automatic sent to the user by system within 5 days (daily) before the " +"password expires" +msgstr "" + +#: settings/serializers/security.py:89 +msgid "Number of repeated historical passwords" +msgstr "" + +#: settings/serializers/security.py:91 +msgid "" +"Tip: When the user resets the password, it cannot be the previous n " +"historical passwords of the user" +msgstr "" + +#: settings/serializers/security.py:96 +msgid "Only single device login" +msgstr "" + +#: settings/serializers/security.py:97 +msgid "Next device login, pre login will be logout" +msgstr "" + +#: settings/serializers/security.py:100 +msgid "Only exist user login" +msgstr "" + +#: settings/serializers/security.py:101 +msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" +msgstr "" + +#: settings/serializers/security.py:104 +msgid "Only from source login" +msgstr "" + +#: settings/serializers/security.py:105 +msgid "Only log in from the user source property" +msgstr "" + +#: settings/serializers/security.py:109 +msgid "MFA verify TTL" +msgstr "" + +#: settings/serializers/security.py:111 +msgid "" +"Unit: second, The verification MFA takes effect only when you view the " +"account password" +msgstr "" + +#: settings/serializers/security.py:116 +msgid "Enable Login dynamic code" +msgstr "" + +#: settings/serializers/security.py:117 +msgid "" +"The password and additional code are sent to a third party authentication " +"system for verification" +msgstr "" + +#: settings/serializers/security.py:122 +msgid "MFA in login page" +msgstr "" + +#: settings/serializers/security.py:123 +msgid "Eu security regulations(GDPR) require MFA to be on the login page" +msgstr "" + +#: settings/serializers/security.py:126 +msgid "Enable Login captcha" +msgstr "" + +#: settings/serializers/security.py:127 +msgid "Enable captcha to prevent robot authentication" +msgstr "" + +#: settings/serializers/security.py:146 +msgid "Security" +msgstr "" + +#: settings/serializers/security.py:149 +msgid "Enable terminal register" +msgstr "" + +#: settings/serializers/security.py:151 +msgid "" +"Allow terminal register, after all terminal setup, you should disable this " +"for security" +msgstr "" + +#: settings/serializers/security.py:155 +msgid "Enable watermark" +msgstr "" + +#: settings/serializers/security.py:156 +msgid "Enabled, the web session and replay contains watermark information" +msgstr "" + +#: settings/serializers/security.py:160 +msgid "Connection max idle time" +msgstr "" + +#: settings/serializers/security.py:161 +msgid "If idle time more than it, disconnect connection Unit: minute" +msgstr "" + +#: settings/serializers/security.py:164 +msgid "Remember manual auth" +msgstr "" + +#: settings/serializers/security.py:167 +msgid "Enable change auth secure mode" +msgstr "" + +#: settings/serializers/security.py:170 +msgid "Insecure command alert" +msgstr "" + +#: settings/serializers/security.py:173 +msgid "Email recipient" +msgstr "" + +#: settings/serializers/security.py:174 +msgid "Multiple user using , split" +msgstr "" + +#: settings/serializers/security.py:177 +msgid "Batch command execution" +msgstr "" + +#: settings/serializers/security.py:178 +msgid "Allow user run batch command or not using ansible" +msgstr "" + +#: settings/serializers/security.py:181 +msgid "Session share" +msgstr "" + +#: settings/serializers/security.py:182 +msgid "Enabled, Allows user active session to be shared with other users" +msgstr "" + +#: settings/serializers/security.py:185 +msgid "Remote Login Protection" +msgstr "" + +#: settings/serializers/security.py:187 +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 " +"a remote login reminder" +msgstr "" + +#: settings/serializers/terminal.py:15 +msgid "Auto" +msgstr "" + +#: settings/serializers/terminal.py:21 +msgid "Password auth" +msgstr "" + +#: settings/serializers/terminal.py:23 +msgid "Public key auth" +msgstr "" + +#: settings/serializers/terminal.py:24 +msgid "" +"Tips: If use other auth method, like AD/LDAP, you should disable this to " +"avoid being able to log in after deleting" +msgstr "" + +#: settings/serializers/terminal.py:28 +msgid "List sort by" +msgstr "" + +#: settings/serializers/terminal.py:31 +msgid "List page size" +msgstr "" + +#: settings/serializers/terminal.py:34 +msgid "Telnet login regex" +msgstr "" + +#: settings/serializers/terminal.py:35 +msgid "" +"Tips: The login success message varies with devices. if you cannot log in to " +"the device through Telnet, set this parameter" +msgstr "" + +#: settings/serializers/terminal.py:38 +msgid "Enable database proxy" +msgstr "" + +#: settings/serializers/terminal.py:39 +msgid "Enable Razor" +msgstr "" + +#: settings/serializers/terminal.py:40 +msgid "Enable SSH Client" +msgstr "" + +#: settings/serializers/terminal.py:51 +msgid "Default graphics resolution" +msgstr "" + +#: settings/serializers/terminal.py:52 +msgid "" +"Tip: Default resolution to use when connecting graphical assets in Luna pages" +msgstr "" + +#: settings/utils/ldap.py:467 +msgid "ldap:// or ldaps:// protocol is used." +msgstr "" + +#: settings/utils/ldap.py:478 +msgid "Host or port is disconnected: {}" +msgstr "" + +#: settings/utils/ldap.py:480 +msgid "The port is not the port of the LDAP service: {}" +msgstr "" + +#: settings/utils/ldap.py:482 +msgid "Please add certificate: {}" +msgstr "" + +#: settings/utils/ldap.py:486 settings/utils/ldap.py:513 +#: settings/utils/ldap.py:543 settings/utils/ldap.py:571 +msgid "Unknown error: {}" +msgstr "" + +#: settings/utils/ldap.py:500 +msgid "Bind DN or Password incorrect" +msgstr "" + +#: settings/utils/ldap.py:507 +msgid "Please enter Bind DN: {}" +msgstr "" + +#: settings/utils/ldap.py:509 +msgid "Please enter Password: {}" +msgstr "" + +#: settings/utils/ldap.py:511 +msgid "Please enter correct Bind DN and Password: {}" +msgstr "" + +#: settings/utils/ldap.py:529 +msgid "Invalid User OU or User search filter: {}" +msgstr "" + +#: settings/utils/ldap.py:560 +msgid "LDAP User attr map not include: {}" +msgstr "" + +#: settings/utils/ldap.py:567 +msgid "LDAP User attr map is not dict" +msgstr "" + +#: settings/utils/ldap.py:586 +msgid "LDAP authentication is not enabled" +msgstr "" + +#: settings/utils/ldap.py:604 +msgid "Error (Invalid LDAP server): {}" +msgstr "" + +#: settings/utils/ldap.py:606 +msgid "Error (Invalid Bind DN): {}" +msgstr "" + +#: settings/utils/ldap.py:608 +msgid "Error (Invalid LDAP User attr map): {}" +msgstr "" + +#: settings/utils/ldap.py:610 +msgid "Error (Invalid User OU or User search filter): {}" +msgstr "" + +#: settings/utils/ldap.py:612 +msgid "Error (Not enabled LDAP authentication): {}" +msgstr "" + +#: settings/utils/ldap.py:614 +msgid "Error (Unknown): {}" +msgstr "" + +#: settings/utils/ldap.py:617 +msgid "Succeed: Match {} s user" +msgstr "" + +#: settings/utils/ldap.py:650 +msgid "Authentication failed (configuration incorrect): {}" +msgstr "" + +#: settings/utils/ldap.py:654 +msgid "Authentication failed (username or password incorrect): {}" +msgstr "" + +#: settings/utils/ldap.py:656 +msgid "Authentication failed (Unknown): {}" +msgstr "" + +#: settings/utils/ldap.py:659 +msgid "Authentication success: {}" +msgstr "" + +#: templates/_csv_import_export.html:8 +msgid "Export" +msgstr "" + +#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 +msgid "Import" +msgstr "" + +#: templates/_csv_import_modal.html:12 +msgid "Download the imported template or use the exported CSV file format" +msgstr "" + +#: templates/_csv_import_modal.html:13 +msgid "Download the import template" +msgstr "" + +#: templates/_csv_import_modal.html:17 templates/_csv_update_modal.html:17 +msgid "Select the CSV file to import" +msgstr "" + +#: templates/_csv_import_modal.html:39 templates/_csv_update_modal.html:42 +msgid "Please select file" +msgstr "" + +#: templates/_csv_update_modal.html:12 +msgid "Download the update template or use the exported CSV file format" +msgstr "" + +#: templates/_csv_update_modal.html:13 +msgid "Download the update template" +msgstr "" + +#: templates/_header_bar.html:12 +msgid "Help" +msgstr "" + +#: templates/_header_bar.html:19 +msgid "Docs" +msgstr "" + +#: templates/_header_bar.html:25 +msgid "Commercial support" +msgstr "" + +#: templates/_header_bar.html:76 users/forms/profile.py:44 +msgid "Profile" +msgstr "" + +#: templates/_header_bar.html:79 +msgid "Admin page" +msgstr "" + +#: templates/_header_bar.html:81 +msgid "User page" +msgstr "" + +#: templates/_header_bar.html:84 +msgid "API Key" +msgstr "" + +#: templates/_header_bar.html:85 +msgid "Logout" +msgstr "" + +#: templates/_message.html:6 +msgid "" +"\n" +" Your account has expired, please contact the administrator.\n" +" " +msgstr "" + +#: templates/_message.html:13 +msgid "Your account will at" +msgstr "" + +#: templates/_message.html:13 templates/_message.html:30 +msgid "expired. " +msgstr "" + +#: templates/_message.html:23 +#, python-format +msgid "" +"\n" +" Your password has expired, please click this link update password.\n" +" " +msgstr "" + +#: templates/_message.html:30 +msgid "Your password will at" +msgstr "" + +#: templates/_message.html:31 +#, python-format +msgid "" +"\n" +" please click this " +"link to update your password.\n" +" " +msgstr "" + +#: templates/_message.html:43 +#, python-format +msgid "" +"\n" +" Your information was incomplete. Please click this link to complete your information.\n" +" " +msgstr "" + +#: templates/_message.html:56 +#, python-format +msgid "" +"\n" +" Your ssh public key not set or expired. Please click this link to update\n" +" " +msgstr "" + +#: templates/_mfa_login_field.html:28 +msgid "Send verification code" +msgstr "" + +#: templates/_mfa_login_field.html:106 +#: users/templates/users/forgot_password.html:129 +msgid "Wait: " +msgstr "" + +#: templates/_mfa_login_field.html:116 +#: users/templates/users/forgot_password.html:145 +msgid "The verification code has been sent" +msgstr "" + +#: templates/_without_nav_base.html:26 +msgid "Home page" +msgstr "" + +#: templates/resource_download.html:18 templates/resource_download.html:31 +msgid "Client" +msgstr "" + +#: templates/resource_download.html:20 +msgid "" +"JumpServer Client, currently used to launch the client, now only support " +"launch RDP SSH client, The Telnet client will next" +msgstr "" + +#: templates/resource_download.html:31 +msgid "Microsoft" +msgstr "" + +#: templates/resource_download.html:31 +msgid "Official" +msgstr "" + +#: templates/resource_download.html:33 +msgid "" +"macOS needs to download the client to connect RDP asset, which comes with " +"Windows" +msgstr "" + +#: templates/resource_download.html:42 +msgid "Windows Remote application publisher tools" +msgstr "" + +#: templates/resource_download.html:43 +msgid "" +"OpenSSH is a program used to connect remote applications in the Windows " +"Remote Application Publisher" +msgstr "" + +#: templates/resource_download.html:48 +msgid "" +"Jmservisor is the program used to pull up remote applications in Windows " +"Remote Application publisher" +msgstr "" + +#: templates/resource_download.html:57 +msgid "Offline video player" +msgstr "" + +#: terminal/api/component/endpoint.py:31 +msgid "Not found protocol query params" +msgstr "" + +#: terminal/api/component/storage.py:28 +msgid "Deleting the default storage is not allowed" +msgstr "" + +#: terminal/api/component/storage.py:31 +msgid "Cannot delete storage that is being used" +msgstr "" + +#: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 +msgid "Command storages" +msgstr "" + +#: terminal/api/component/storage.py:79 +msgid "Invalid" +msgstr "" + +#: terminal/api/component/storage.py:119 +msgid "Test failure: {}" +msgstr "" + +#: terminal/api/component/storage.py:122 +msgid "Test successful" +msgstr "" + +#: terminal/api/component/storage.py:124 +msgid "Test failure: Account invalid" +msgstr "" + +#: terminal/api/component/terminal.py:38 +msgid "Have online sessions" +msgstr "" + +#: terminal/api/session/session.py:217 +msgid "Session does not exist: {}" +msgstr "" + +#: terminal/api/session/session.py:220 +msgid "Session is finished or the protocol not supported" +msgstr "" + +#: terminal/api/session/session.py:233 +msgid "User does not have permission" +msgstr "" + +#: terminal/api/session/sharing.py:29 +msgid "Secure session sharing settings is disabled" +msgstr "" + +#: terminal/apps.py:9 +msgid "Terminals" +msgstr "" + +#: terminal/backends/command/models.py:16 +msgid "Ordinary" +msgstr "" + +#: terminal/backends/command/models.py:17 +msgid "Dangerous" +msgstr "" + +#: terminal/backends/command/models.py:23 +msgid "Input" +msgstr "" + +#: terminal/backends/command/models.py:24 +#: terminal/backends/command/serializers.py:38 +msgid "Output" +msgstr "" + +#: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 +#: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 +#: terminal/templates/terminal/_msg_command_alert.html:10 +#: tickets/models/ticket/command_confirm.py:17 +msgid "Session" +msgstr "" + +#: terminal/backends/command/models.py:26 +#: terminal/backends/command/serializers.py:18 +msgid "Risk level" +msgstr "" + +#: terminal/backends/command/serializers.py:16 +msgid "Session ID" +msgstr "" + +#: terminal/backends/command/serializers.py:37 +msgid "Account " +msgstr "" + +#: terminal/backends/command/serializers.py:39 +msgid "Risk level display" +msgstr "" + +#: terminal/backends/command/serializers.py:40 +msgid "Timestamp" +msgstr "" + +#: terminal/backends/command/serializers.py:42 +#: terminal/models/component/terminal.py:85 +msgid "Remote Address" +msgstr "" + +#: terminal/const.py:37 +msgid "Critical" +msgstr "" + +#: terminal/const.py:38 +msgid "High" +msgstr "" + +#: terminal/const.py:39 users/templates/users/reset_password.html:50 +msgid "Normal" +msgstr "" + +#: terminal/const.py:40 +msgid "Offline" +msgstr "" + +#: terminal/const.py:81 terminal/const.py:82 terminal/const.py:83 +#: terminal/const.py:84 terminal/const.py:85 +msgid "DB Client" +msgstr "" + +#: terminal/exceptions.py:8 +msgid "Bulk create not support" +msgstr "" + +#: terminal/exceptions.py:13 +msgid "Storage is invalid" +msgstr "" + +#: terminal/models/applet/applet.py:23 +msgid "Author" +msgstr "" + +#: terminal/models/applet/applet.py:27 +msgid "Tags" +msgstr "" + +#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 +msgid "Hosts" +msgstr "" + +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 +msgid "Applet" +msgstr "" + +#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 +msgid "Deploy options" +msgstr "" + +#: terminal/models/applet/host.py:19 +msgid "Inited" +msgstr "" + +#: terminal/models/applet/host.py:20 +msgid "Date inited" +msgstr "" + +#: terminal/models/applet/host.py:21 +msgid "Date synced" +msgstr "" + +#: terminal/models/applet/host.py:102 +msgid "Hosting" +msgstr "" + +#: terminal/models/applet/host.py:103 +msgid "Initial" +msgstr "" + +#: terminal/models/component/endpoint.py:15 +msgid "HTTPS Port" +msgstr "" + +#: terminal/models/component/endpoint.py:16 +msgid "HTTP Port" +msgstr "" + +#: terminal/models/component/endpoint.py:17 +msgid "SSH Port" +msgstr "" + +#: terminal/models/component/endpoint.py:18 +msgid "RDP Port" +msgstr "" + +#: terminal/models/component/endpoint.py:25 +#: terminal/models/component/endpoint.py:94 terminal/serializers/endpoint.py:57 +#: 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 +msgid "Endpoint" +msgstr "" + +#: terminal/models/component/endpoint.py:87 +msgid "IP group" +msgstr "" + +#: terminal/models/component/endpoint.py:99 +msgid "Endpoint rule" +msgstr "" + +#: terminal/models/component/status.py:14 +msgid "Session Online" +msgstr "" + +#: terminal/models/component/status.py:15 +msgid "CPU Load" +msgstr "" + +#: terminal/models/component/status.py:16 +msgid "Memory Used" +msgstr "" + +#: terminal/models/component/status.py:17 +msgid "Disk Used" +msgstr "" + +#: terminal/models/component/status.py:18 +msgid "Connections" +msgstr "" + +#: terminal/models/component/status.py:19 +msgid "Threads" +msgstr "" + +#: terminal/models/component/status.py:20 +msgid "Boot Time" +msgstr "" + +#: terminal/models/component/storage.py:27 +msgid "Default storage" +msgstr "" + +#: terminal/models/component/storage.py:140 +#: terminal/models/component/terminal.py:86 +msgid "Command storage" +msgstr "" + +#: terminal/models/component/storage.py:200 +#: terminal/models/component/terminal.py:87 +msgid "Replay storage" +msgstr "" + +#: terminal/models/component/terminal.py:83 +msgid "type" +msgstr "" + +#: terminal/models/component/terminal.py:88 +msgid "Application User" +msgstr "" + +#: terminal/models/component/terminal.py:161 +msgid "Can view terminal config" +msgstr "" + +#: terminal/models/session/command.py:66 +msgid "Command record" +msgstr "" + +#: terminal/models/session/replay.py:12 +msgid "Session replay" +msgstr "" + +#: terminal/models/session/replay.py:14 +msgid "Can upload session replay" +msgstr "" + +#: terminal/models/session/replay.py:15 +msgid "Can download session replay" +msgstr "" + +#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 +msgid "Login from" +msgstr "" + +#: terminal/models/session/session.py:40 +msgid "Replay" +msgstr "" + +#: terminal/models/session/session.py:44 +msgid "Date end" +msgstr "" + +#: terminal/models/session/session.py:236 +msgid "Session record" +msgstr "" + +#: terminal/models/session/session.py:238 +msgid "Can monitor session" +msgstr "" + +#: terminal/models/session/session.py:239 +msgid "Can share session" +msgstr "" + +#: terminal/models/session/session.py:240 +msgid "Can terminate session" +msgstr "" + +#: terminal/models/session/session.py:241 +msgid "Can validate session action perm" +msgstr "" + +#: terminal/models/session/sharing.py:31 +msgid "Expired time (min)" +msgstr "" + +#: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 +msgid "Session sharing" +msgstr "" + +#: terminal/models/session/sharing.py:39 +msgid "Can add super session sharing" +msgstr "" + +#: terminal/models/session/sharing.py:66 +msgid "Link not active" +msgstr "" + +#: terminal/models/session/sharing.py:68 +msgid "Link expired" +msgstr "" + +#: terminal/models/session/sharing.py:70 +msgid "User not allowed to join" +msgstr "" + +#: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 +msgid "Joiner" +msgstr "" + +#: terminal/models/session/sharing.py:90 +msgid "Date joined" +msgstr "" + +#: terminal/models/session/sharing.py:93 +msgid "Date left" +msgstr "" + +#: terminal/models/session/sharing.py:116 +msgid "Session join record" +msgstr "" + +#: terminal/models/session/sharing.py:132 +msgid "Invalid verification code" +msgstr "" + +#: terminal/notifications.py:22 +msgid "Sessions" +msgstr "" + +#: terminal/notifications.py:68 +msgid "Danger command alert" +msgstr "" + +#: terminal/notifications.py:95 terminal/notifications.py:143 +msgid "Level" +msgstr "" + +#: terminal/notifications.py:113 +msgid "Batch danger command alert" +msgstr "" + +#: terminal/serializers/applet.py:16 +msgid "Published" +msgstr "" + +#: terminal/serializers/applet.py:17 +msgid "Unpublished" +msgstr "" + +#: terminal/serializers/applet.py:18 +msgid "Not match" +msgstr "" + +#: terminal/serializers/applet.py:32 +msgid "Icon" +msgstr "" + +#: terminal/serializers/applet_host.py:21 +msgid "Per Session" +msgstr "" + +#: terminal/serializers/applet_host.py:22 +msgid "Per Device" +msgstr "" + +#: terminal/serializers/applet_host.py:28 +msgid "RDS Licensing" +msgstr "" + +#: terminal/serializers/applet_host.py:29 +msgid "RDS License Server" +msgstr "" + +#: terminal/serializers/applet_host.py:30 +msgid "RDS Licensing Mode" +msgstr "" + +#: terminal/serializers/applet_host.py:32 +msgid "RDS fSingleSessionPerUser" +msgstr "" + +#: terminal/serializers/applet_host.py:33 +msgid "RDS Max Disconnection Time" +msgstr "" + +#: terminal/serializers/applet_host.py:34 +msgid "RDS Remote App Logoff Time Limit" +msgstr "" + +#: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 +msgid "Load status" +msgstr "" + +#: terminal/serializers/endpoint.py:14 +msgid "Magnus listen db port" +msgstr "" + +#: terminal/serializers/endpoint.py:17 +msgid "Magnus Listen port range" +msgstr "" + +#: terminal/serializers/endpoint.py:19 +msgid "" +"The range of ports that Magnus listens on is modified in the configuration " +"file" +msgstr "" + +#: terminal/serializers/endpoint.py:51 +msgid "" +"If asset IP addresses under different endpoints conflict, use asset labels" +msgstr "" + +#: terminal/serializers/session.py:17 terminal/serializers/session.py:42 +msgid "Terminal display" +msgstr "" + +#: terminal/serializers/session.py:33 +msgid "User ID" +msgstr "" + +#: terminal/serializers/session.py:34 +msgid "Asset ID" +msgstr "" + +#: terminal/serializers/session.py:35 +msgid "Login from display" +msgstr "" + +#: terminal/serializers/session.py:37 +msgid "Can replay" +msgstr "" + +#: terminal/serializers/session.py:38 +msgid "Can join" +msgstr "" + +#: terminal/serializers/session.py:39 +msgid "Terminal ID" +msgstr "" + +#: terminal/serializers/session.py:40 +msgid "Is finished" +msgstr "" + +#: terminal/serializers/session.py:41 +msgid "Can terminate" +msgstr "" + +#: terminal/serializers/session.py:47 +msgid "Command amount" +msgstr "" + +#: terminal/serializers/storage.py:20 +msgid "Endpoint invalid: remove path `{}`" +msgstr "" + +#: terminal/serializers/storage.py:26 +msgid "Bucket" +msgstr "" + +#: terminal/serializers/storage.py:30 +#: xpack/plugins/cloud/serializers/account_attrs.py:17 +msgid "Access key id" +msgstr "" + +#: terminal/serializers/storage.py:34 +#: xpack/plugins/cloud/serializers/account_attrs.py:20 +msgid "Access key secret" +msgstr "" + +#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:219 +msgid "Region" +msgstr "" + +#: terminal/serializers/storage.py:109 +msgid "Container name" +msgstr "" + +#: terminal/serializers/storage.py:112 +msgid "Account key" +msgstr "" + +#: terminal/serializers/storage.py:115 +msgid "Endpoint suffix" +msgstr "" + +#: terminal/serializers/storage.py:135 +msgid "The address format is incorrect" +msgstr "" + +#: terminal/serializers/storage.py:142 +msgid "Host invalid" +msgstr "" + +#: terminal/serializers/storage.py:145 +msgid "Port invalid" +msgstr "" + +#: terminal/serializers/storage.py:160 +msgid "Index by date" +msgstr "" + +#: terminal/serializers/storage.py:161 +msgid "Whether to create an index by date" +msgstr "" + +#: terminal/serializers/storage.py:164 +msgid "Index" +msgstr "" + +#: terminal/serializers/storage.py:166 +msgid "Doc type" +msgstr "" + +#: terminal/serializers/storage.py:168 +msgid "Ignore Certificate Verification" +msgstr "" + +#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 +msgid "Not found" +msgstr "" + +#: terminal/templates/terminal/_msg_command_alert.html:10 +msgid "view" +msgstr "" + +#: terminal/utils/db_port_mapper.py:64 +msgid "" +"No available port is matched. The number of databases may have exceeded the " +"number of ports open to the database agent service, Contact the " +"administrator to open more ports." +msgstr "" + +#: terminal/utils/db_port_mapper.py:90 +msgid "" +"No ports can be used, check and modify the limit on the number of ports that " +"Magnus listens on in the configuration file." +msgstr "" + +#: terminal/utils/db_port_mapper.py:92 +msgid "All available port count: {}, Already use port count: {}" +msgstr "" + +#: tickets/apps.py:7 +msgid "Tickets" +msgstr "" + +#: tickets/const.py:9 +msgid "Apply for asset" +msgstr "" + +#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 +msgid "Open" +msgstr "" + +#: tickets/const.py:18 tickets/const.py:31 +msgid "Reopen" +msgstr "" + +#: tickets/const.py:19 tickets/const.py:32 +msgid "Approved" +msgstr "" + +#: tickets/const.py:20 tickets/const.py:33 +msgid "Rejected" +msgstr "" + +#: tickets/const.py:30 tickets/const.py:38 +msgid "Closed" +msgstr "" + +#: tickets/const.py:46 +msgid "Approve" +msgstr "" + +#: tickets/const.py:50 +msgid "One level" +msgstr "" + +#: tickets/const.py:51 +msgid "Two level" +msgstr "" + +#: tickets/const.py:55 +msgid "Org admin" +msgstr "" + +#: tickets/const.py:56 +msgid "Custom user" +msgstr "" + +#: tickets/const.py:57 +msgid "Super admin" +msgstr "" + +#: tickets/const.py:58 +msgid "Super admin and org admin" +msgstr "" + +#: tickets/errors.py:9 +msgid "Ticket already closed" +msgstr "" + +#: tickets/handlers/apply_asset.py:36 +msgid "" +"Created by the ticket ticket title: {} ticket applicant: {} ticket " +"processor: {} ticket ID: {}" +msgstr "" + +#: tickets/handlers/base.py:84 +msgid "Change field" +msgstr "" + +#: tickets/handlers/base.py:84 +msgid "Before change" +msgstr "" + +#: tickets/handlers/base.py:84 +msgid "After change" +msgstr "" + +#: tickets/handlers/base.py:96 +msgid "{} {} the ticket" +msgstr "" + +#: tickets/models/comment.py:14 +msgid "common" +msgstr "" + +#: tickets/models/comment.py:23 +msgid "User display name" +msgstr "" + +#: tickets/models/comment.py:24 +msgid "Body" +msgstr "" + +#: tickets/models/flow.py:20 tickets/models/flow.py:62 +#: tickets/models/ticket/general.py:39 +msgid "Approve level" +msgstr "" + +#: tickets/models/flow.py:25 tickets/serializers/flow.py:18 +msgid "Approve strategy" +msgstr "" + +#: tickets/models/flow.py:30 tickets/serializers/flow.py:20 +msgid "Assignees" +msgstr "" + +#: tickets/models/flow.py:34 +msgid "Ticket flow approval rule" +msgstr "" + +#: tickets/models/flow.py:67 +msgid "Ticket flow" +msgstr "" + +#: tickets/models/relation.py:10 +msgid "Ticket session relation" +msgstr "" + +#: tickets/models/ticket/apply_application.py:10 +#: tickets/models/ticket/apply_asset.py:13 +msgid "Permission name" +msgstr "" + +#: tickets/models/ticket/apply_application.py:19 +msgid "Apply applications" +msgstr "" + +#: tickets/models/ticket/apply_application.py:22 +msgid "Apply system users" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:9 +#: tickets/serializers/ticket/apply_asset.py:14 +msgid "Select at least one asset or node" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:14 +#: tickets/serializers/ticket/apply_asset.py:19 +msgid "Apply nodes" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:16 +#: tickets/serializers/ticket/apply_asset.py:18 +msgid "Apply assets" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:17 +msgid "Apply accounts" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:10 +msgid "Run user" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:12 +msgid "Run asset" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:13 +msgid "Run command" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:14 +msgid "Run account" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:21 +msgid "From cmd filter" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:25 +msgid "From cmd filter rule" +msgstr "" + +#: tickets/models/ticket/general.py:74 +msgid "Ticket step" +msgstr "" + +#: tickets/models/ticket/general.py:92 +msgid "Ticket assignee" +msgstr "" + +#: tickets/models/ticket/general.py:271 +msgid "Title" +msgstr "" + +#: tickets/models/ticket/general.py:287 +msgid "Applicant" +msgstr "" + +#: tickets/models/ticket/general.py:291 +msgid "TicketFlow" +msgstr "" + +#: tickets/models/ticket/general.py:294 +msgid "Approval step" +msgstr "" + +#: tickets/models/ticket/general.py:297 +msgid "Relation snapshot" +msgstr "" + +#: tickets/models/ticket/general.py:391 +msgid "Please try again" +msgstr "" + +#: tickets/models/ticket/general.py:424 +msgid "Super ticket" +msgstr "" + +#: tickets/models/ticket/login_asset_confirm.py:11 +msgid "Login user" +msgstr "" + +#: tickets/models/ticket/login_asset_confirm.py:14 +msgid "Login asset" +msgstr "" + +#: tickets/models/ticket/login_asset_confirm.py:17 +msgid "Login account" +msgstr "" + +#: tickets/models/ticket/login_confirm.py:12 +msgid "Login datetime" +msgstr "" + +#: tickets/notifications.py:63 +msgid "Ticket basic info" +msgstr "" + +#: tickets/notifications.py:64 +msgid "Ticket applied info" +msgstr "" + +#: tickets/notifications.py:109 +msgid "Your has a new ticket, applicant - {}" +msgstr "" + +#: tickets/notifications.py:113 +msgid "{}: New Ticket - {} ({})" +msgstr "" + +#: tickets/notifications.py:157 +msgid "Your ticket has been processed, processor - {}" +msgstr "" + +#: tickets/notifications.py:161 +msgid "Ticket has processed - {} ({})" +msgstr "" + +#: tickets/serializers/flow.py:21 +msgid "Assignees display" +msgstr "" + +#: tickets/serializers/flow.py:47 +msgid "Please select the Assignees" +msgstr "" + +#: tickets/serializers/flow.py:75 +msgid "The current organization type already exists" +msgstr "" + +#: tickets/serializers/super_ticket.py:11 +msgid "Processor" +msgstr "" + +#: tickets/serializers/ticket/apply_asset.py:20 +msgid "Apply actions" +msgstr "" + +#: tickets/serializers/ticket/common.py:15 +#: tickets/serializers/ticket/common.py:77 +msgid "Created by ticket ({}-{})" +msgstr "" + +#: tickets/serializers/ticket/common.py:67 +msgid "The expiration date should be greater than the start date" +msgstr "" + +#: tickets/serializers/ticket/common.py:84 +msgid "Permission named `{}` already exists" +msgstr "" + +#: tickets/serializers/ticket/ticket.py:96 +msgid "The ticket flow `{}` does not exist" +msgstr "" + +#: tickets/templates/tickets/_msg_ticket.html:20 +msgid "View details" +msgstr "" + +#: tickets/templates/tickets/_msg_ticket.html:25 +msgid "Direct approval" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:11 +msgid "Ticket information" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:29 +#: tickets/views/approve.py:38 +msgid "Ticket approval" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:45 +msgid "Approval" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:54 +msgid "Go Login" +msgstr "" + +#: tickets/views/approve.py:39 +msgid "" +"This ticket does not exist, the process has ended, or this link has expired" +msgstr "" + +#: tickets/views/approve.py:68 +msgid "Click the button below to approve or reject" +msgstr "" + +#: tickets/views/approve.py:70 +msgid "After successful authentication, this ticket can be approved directly" +msgstr "" + +#: tickets/views/approve.py:92 +msgid "Illegal approval action" +msgstr "" + +#: tickets/views/approve.py:105 +msgid "This user is not authorized to approve this ticket" +msgstr "" + +#: users/api/user.py:183 +msgid "Could not reset self otp, use profile reset instead" +msgstr "" + +#: users/apps.py:9 +msgid "Users" +msgstr "" + +#: users/const.py:10 +msgid "System administrator" +msgstr "" + +#: users/const.py:11 +msgid "System auditor" +msgstr "" + +#: users/const.py:12 +msgid "Organization administrator" +msgstr "" + +#: users/const.py:13 +msgid "Organization auditor" +msgstr "" + +#: users/const.py:18 +msgid "Reset link will be generated and sent to the user" +msgstr "" + +#: users/const.py:19 +msgid "Set password" +msgstr "" + +#: users/exceptions.py:10 +msgid "MFA not enabled" +msgstr "" + +#: users/exceptions.py:20 +msgid "MFA method not support" +msgstr "" + +#: users/forms/profile.py:50 +msgid "" +"When enabled, you will enter the MFA binding process the next time you log " +"in. you can also directly bind in \"personal information -> quick " +"modification -> change MFA Settings\"!" +msgstr "" + +#: users/forms/profile.py:61 +msgid "* Enable MFA to make the account more secure." +msgstr "" + +#: users/forms/profile.py:70 +msgid "" +"In order to protect you and your company, please keep your account, password " +"and key sensitive information properly. (for example: setting complex " +"password, enabling MFA)" +msgstr "" + +#: users/forms/profile.py:77 +msgid "Finish" +msgstr "" + +#: users/forms/profile.py:84 +msgid "New password" +msgstr "" + +#: users/forms/profile.py:89 +msgid "Confirm password" +msgstr "" + +#: users/forms/profile.py:97 +msgid "Password does not match" +msgstr "" + +#: users/forms/profile.py:118 +msgid "Old password" +msgstr "" + +#: users/forms/profile.py:128 +msgid "Old password error" +msgstr "" + +#: users/forms/profile.py:138 +msgid "Automatically configure and download the SSH key" +msgstr "" + +#: users/forms/profile.py:140 +msgid "ssh public key" +msgstr "" + +#: users/forms/profile.py:141 +msgid "ssh-rsa AAAA..." +msgstr "" + +#: users/forms/profile.py:142 +msgid "Paste your id_rsa.pub here." +msgstr "" + +#: users/forms/profile.py:155 +msgid "Public key should not be the same as your old one." +msgstr "" + +#: users/forms/profile.py:159 users/serializers/profile.py:100 +#: users/serializers/profile.py:183 users/serializers/profile.py:210 +msgid "Not a valid ssh public key" +msgstr "" + +#: users/forms/profile.py:170 users/models/user.py:708 +msgid "Public key" +msgstr "" + +#: users/models/user.py:561 +msgid "Force enable" +msgstr "" + +#: users/models/user.py:631 +msgid "Local" +msgstr "" + +#: users/models/user.py:687 users/serializers/user.py:204 +msgid "Is service account" +msgstr "" + +#: users/models/user.py:689 +msgid "Avatar" +msgstr "" + +#: users/models/user.py:692 +msgid "Wechat" +msgstr "" + +#: users/models/user.py:695 +msgid "Phone" +msgstr "" + +#: users/models/user.py:701 +msgid "OTP secret key" +msgstr "" + +#: users/models/user.py:705 +msgid "Private key" +msgstr "" + +#: users/models/user.py:711 +msgid "Secret key" +msgstr "" + +#: users/models/user.py:716 users/serializers/profile.py:149 +#: users/serializers/user.py:201 +msgid "Is first login" +msgstr "" + +#: users/models/user.py:727 +msgid "Source" +msgstr "" + +#: users/models/user.py:731 +msgid "Date password last updated" +msgstr "" + +#: users/models/user.py:734 +msgid "Need update password" +msgstr "" + +#: users/models/user.py:909 +msgid "Can invite user" +msgstr "" + +#: users/models/user.py:910 +msgid "Can remove user" +msgstr "" + +#: users/models/user.py:911 +msgid "Can match user" +msgstr "" + +#: users/models/user.py:920 +msgid "Administrator" +msgstr "" + +#: users/models/user.py:923 +msgid "Administrator is the super user of system" +msgstr "" + +#: users/models/user.py:948 +msgid "User password history" +msgstr "" + +#: users/notifications.py:55 +#: users/templates/users/_msg_password_expire_reminder.html:17 +#: users/templates/users/reset_password.html:5 +#: users/templates/users/reset_password.html:6 +msgid "Reset password" +msgstr "" + +#: users/notifications.py:85 users/views/profile/reset.py:194 +msgid "Reset password success" +msgstr "" + +#: users/notifications.py:117 +msgid "Reset public key success" +msgstr "" + +#: users/notifications.py:143 +msgid "Password is about expire" +msgstr "" + +#: users/notifications.py:171 +msgid "Account is about expire" +msgstr "" + +#: users/notifications.py:193 +msgid "Reset SSH Key" +msgstr "" + +#: users/notifications.py:214 +msgid "Reset MFA" +msgstr "" + +#: users/serializers/profile.py:30 +msgid "The old password is incorrect" +msgstr "" + +#: users/serializers/profile.py:37 users/serializers/profile.py:197 +msgid "Password does not match security rules" +msgstr "" + +#: users/serializers/profile.py:41 +msgid "The new password cannot be the last {} passwords" +msgstr "" + +#: users/serializers/profile.py:49 users/serializers/profile.py:71 +msgid "The newly set password is inconsistent" +msgstr "" + +#: users/serializers/user.py:30 +msgid "System roles" +msgstr "" + +#: users/serializers/user.py:35 +msgid "Org roles" +msgstr "" + +#: users/serializers/user.py:38 +msgid "System roles display" +msgstr "" + +#: users/serializers/user.py:40 +msgid "Org roles display" +msgstr "" + +#: users/serializers/user.py:90 +#: xpack/plugins/change_auth_plan/models/base.py:35 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 +msgid "Password strategy" +msgstr "" + +#: users/serializers/user.py:92 +msgid "MFA enabled" +msgstr "" + +#: users/serializers/user.py:94 +msgid "MFA force enabled" +msgstr "" + +#: users/serializers/user.py:97 +msgid "MFA level display" +msgstr "" + +#: users/serializers/user.py:99 +msgid "Login blocked" +msgstr "" + +#: users/serializers/user.py:102 +msgid "Can public key authentication" +msgstr "" + +#: users/serializers/user.py:206 +msgid "Avatar url" +msgstr "" + +#: users/serializers/user.py:208 +msgid "Groups name" +msgstr "" + +#: users/serializers/user.py:209 +msgid "Source name" +msgstr "" + +#: users/serializers/user.py:210 +msgid "Organization role name" +msgstr "" + +#: users/serializers/user.py:211 +msgid "Super role name" +msgstr "" + +#: users/serializers/user.py:212 +msgid "Total role name" +msgstr "" + +#: users/serializers/user.py:214 +msgid "Is wecom bound" +msgstr "" + +#: users/serializers/user.py:215 +msgid "Is dingtalk bound" +msgstr "" + +#: users/serializers/user.py:216 +msgid "Is feishu bound" +msgstr "" + +#: users/serializers/user.py:217 +msgid "Is OTP bound" +msgstr "" + +#: users/serializers/user.py:219 +msgid "System role name" +msgstr "" + +#: users/serializers/user.py:325 +msgid "Select users" +msgstr "" + +#: users/serializers/user.py:326 +msgid "For security, only list several users" +msgstr "" + +#: users/serializers/user.py:362 +msgid "name not unique" +msgstr "" + +#: users/templates/users/_msg_account_expire_reminder.html:7 +msgid "Your account will expire in" +msgstr "" + +#: users/templates/users/_msg_account_expire_reminder.html:8 +msgid "" +"In order not to affect your normal work, please contact the administrator " +"for confirmation." +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:7 +msgid "Your password will expire in" +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:8 +msgid "" +"For your account security, please click on the link below to update your " +"password in time" +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:11 +msgid "Click here update password" +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:16 +msgid "If your password has expired, please click the link below to" +msgstr "" + +#: users/templates/users/_msg_reset_mfa.html:7 +msgid "Your MFA has been reset by site administrator" +msgstr "" + +#: users/templates/users/_msg_reset_mfa.html:8 +#: users/templates/users/_msg_reset_ssh_key.html:8 +msgid "Please click the link below to set" +msgstr "" + +#: users/templates/users/_msg_reset_mfa.html:11 +#: users/templates/users/_msg_reset_ssh_key.html:11 +msgid "Click here set" +msgstr "" + +#: users/templates/users/_msg_reset_ssh_key.html:7 +msgid "Your ssh public key has been reset by site administrator" +msgstr "" + +#: users/templates/users/_msg_user_created.html:15 +msgid "click here to set your password" +msgstr "" + +#: users/templates/users/forgot_password.html:32 +msgid "Input your email account, that will send a email to your" +msgstr "" + +#: users/templates/users/forgot_password.html:35 +msgid "" +"Enter your mobile number and a verification code will be sent to your phone" +msgstr "" + +#: users/templates/users/forgot_password.html:57 +msgid "Email account" +msgstr "" + +#: users/templates/users/forgot_password.html:61 +msgid "Mobile number" +msgstr "" + +#: users/templates/users/forgot_password.html:68 +msgid "Send" +msgstr "" + +#: users/templates/users/forgot_password.html:72 +#: users/templates/users/forgot_password_previewing.html:30 +msgid "Submit" +msgstr "" + +#: users/templates/users/forgot_password_previewing.html:21 +msgid "Please enter the username for which you want to retrieve the password" +msgstr "" + +#: users/templates/users/mfa_setting.html:24 +msgid "Enable MFA" +msgstr "" + +#: users/templates/users/mfa_setting.html:30 +msgid "MFA force enable, cannot disable" +msgstr "" + +#: users/templates/users/mfa_setting.html:48 +msgid "MFA setting" +msgstr "" + +#: users/templates/users/reset_password.html:23 +msgid "Your password must satisfy" +msgstr "" + +#: users/templates/users/reset_password.html:24 +msgid "Password strength" +msgstr "" + +#: users/templates/users/reset_password.html:48 +msgid "Very weak" +msgstr "" + +#: users/templates/users/reset_password.html:49 +msgid "Weak" +msgstr "" + +#: users/templates/users/reset_password.html:51 +msgid "Medium" +msgstr "" + +#: users/templates/users/reset_password.html:52 +msgid "Strong" +msgstr "" + +#: users/templates/users/reset_password.html:53 +msgid "Very strong" +msgstr "" + +#: users/templates/users/user_otp_check_password.html:6 +msgid "Enable OTP" +msgstr "" + +#: users/templates/users/user_otp_enable_bind.html:6 +msgid "Bind one-time password authenticator" +msgstr "" + +#: users/templates/users/user_otp_enable_bind.html:13 +msgid "" +"Use the MFA Authenticator application to scan the following qr code for a 6-" +"bit verification code" +msgstr "" + +#: users/templates/users/user_otp_enable_bind.html:22 +#: users/templates/users/user_verify_mfa.html:27 +msgid "Six figures" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:6 +msgid "Install app" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:13 +msgid "" +"Download and install the MFA Authenticator application on your phone or " +"applet of WeChat" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:18 +msgid "Android downloads" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:23 +msgid "iPhone downloads" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:26 +msgid "" +"After installation, click the next step to enter the binding page (if " +"installed, go to the next step directly)." +msgstr "" + +#: users/templates/users/user_password_verify.html:8 +#: users/templates/users/user_password_verify.html:9 +msgid "Verify password" +msgstr "" + +#: users/templates/users/user_verify_mfa.html:9 +msgid "Authenticate" +msgstr "" + +#: users/templates/users/user_verify_mfa.html:15 +msgid "" +"The account protection has been opened, please complete the following " +"operations according to the prompts" +msgstr "" + +#: users/templates/users/user_verify_mfa.html:17 +msgid "Open MFA Authenticator and enter the 6-bit dynamic code" +msgstr "" + +#: users/views/profile/otp.py:87 +msgid "Already bound" +msgstr "" + +#: users/views/profile/otp.py:88 +msgid "MFA already bound, disable first, then bound" +msgstr "" + +#: users/views/profile/otp.py:115 +msgid "OTP enable success" +msgstr "" + +#: users/views/profile/otp.py:116 +msgid "OTP enable success, return login page" +msgstr "" + +#: users/views/profile/otp.py:158 +msgid "Disable OTP" +msgstr "" + +#: users/views/profile/otp.py:164 +msgid "OTP disable success" +msgstr "" + +#: users/views/profile/otp.py:165 +msgid "OTP disable success, return login page" +msgstr "" + +#: users/views/profile/password.py:36 users/views/profile/password.py:41 +msgid "Password invalid" +msgstr "" + +#: users/views/profile/reset.py:47 +msgid "" +"Non-local users can log in only from third-party platforms and cannot change " +"their passwords: {}" +msgstr "" + +#: users/views/profile/reset.py:149 users/views/profile/reset.py:160 +msgid "Token invalid or expired" +msgstr "" + +#: users/views/profile/reset.py:165 +msgid "User auth from {}, go there change password" +msgstr "" + +#: users/views/profile/reset.py:172 +msgid "* Your password does not meet the requirements" +msgstr "" + +#: users/views/profile/reset.py:178 +msgid "* The new password cannot be the last {} passwords" +msgstr "" + +#: users/views/profile/reset.py:195 +msgid "Reset password success, return to login page" +msgstr "" + +#: xpack/apps.py:8 +msgid "XPACK" +msgstr "" + +#: xpack/plugins/change_auth_plan/meta.py:9 +#: xpack/plugins/change_auth_plan/models/asset.py:124 +msgid "Change auth plan" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:45 +#: xpack/plugins/change_auth_plan/models/app.py:94 +msgid "Application change auth plan" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:98 +#: xpack/plugins/change_auth_plan/models/app.py:150 +msgid "Application change auth plan execution" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:143 +msgid "App" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:155 +msgid "Application change auth plan task" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:179 +#: xpack/plugins/change_auth_plan/models/asset.py:264 +msgid "Password cannot be set to blank, exit. " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:68 +msgid "Asset change auth plan" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:135 +msgid "Asset change auth plan execution" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:211 +msgid "Change auth plan execution" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:218 +msgid "Asset change auth plan task" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:253 +msgid "This asset does not have a privileged user set: " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:259 +msgid "" +"The password and key of the current asset privileged user cannot be changed: " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:270 +msgid "Public key cannot be set to null, exit. " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:114 +msgid "Change auth plan snapshot" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:184 +msgid "Preflight check" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:185 +msgid "Change auth" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:186 +msgid "Verify auth" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:187 +msgid "Keep auth" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:195 +msgid "Step" +msgstr "" + +#: xpack/plugins/change_auth_plan/serializers/asset.py:30 +msgid "Change Password" +msgstr "" + +#: xpack/plugins/change_auth_plan/serializers/asset.py:31 +msgid "Change SSH Key" +msgstr "" + +#: xpack/plugins/change_auth_plan/serializers/base.py:44 +msgid "Run times" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 +msgid "After many attempts to change the secret, it still failed" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:255 +msgid "Invalid/incorrect password" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:257 +msgid "Failed to connect to the host" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:259 +msgid "Data could not be sent to remote" +msgstr "" + +#: xpack/plugins/cloud/api.py:40 +msgid "Test connection successful" +msgstr "" + +#: xpack/plugins/cloud/api.py:42 +msgid "Test connection failed: {}" +msgstr "" + +#: xpack/plugins/cloud/const.py:8 +msgid "Alibaba Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:9 +msgid "AWS (International)" +msgstr "" + +#: xpack/plugins/cloud/const.py:10 +msgid "AWS (China)" +msgstr "" + +#: xpack/plugins/cloud/const.py:11 +msgid "Azure (China)" +msgstr "" + +#: xpack/plugins/cloud/const.py:12 +msgid "Azure (International)" +msgstr "" + +#: xpack/plugins/cloud/const.py:14 +msgid "Baidu Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:16 +msgid "KingSoft Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:17 +msgid "Tencent Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:18 +msgid "Tencent Cloud (Lighthouse)" +msgstr "" + +#: xpack/plugins/cloud/const.py:19 +msgid "VMware" +msgstr "" + +#: xpack/plugins/cloud/const.py:20 xpack/plugins/cloud/providers/nutanix.py:13 +msgid "Nutanix" +msgstr "" + +#: xpack/plugins/cloud/const.py:21 +msgid "Huawei Private Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:22 +msgid "Qingyun Private Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:23 +msgid "CTYun Private Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:24 +msgid "OpenStack" +msgstr "" + +#: xpack/plugins/cloud/const.py:25 +msgid "Google Cloud Platform" +msgstr "" + +#: xpack/plugins/cloud/const.py:26 +msgid "Fusion Compute" +msgstr "" + +#: xpack/plugins/cloud/const.py:31 +msgid "Private IP" +msgstr "" + +#: xpack/plugins/cloud/const.py:32 +msgid "Public IP" +msgstr "" + +#: xpack/plugins/cloud/const.py:36 +msgid "Instance name" +msgstr "" + +#: xpack/plugins/cloud/const.py:37 +msgid "Instance name and Partial IP" +msgstr "" + +#: xpack/plugins/cloud/const.py:42 +msgid "Succeed" +msgstr "" + +#: xpack/plugins/cloud/const.py:46 +msgid "Unsync" +msgstr "" + +#: xpack/plugins/cloud/const.py:47 +msgid "New Sync" +msgstr "" + +#: xpack/plugins/cloud/const.py:48 +msgid "Synced" +msgstr "" + +#: xpack/plugins/cloud/const.py:49 +msgid "Released" +msgstr "" + +#: xpack/plugins/cloud/meta.py:9 +msgid "Cloud center" +msgstr "" + +#: xpack/plugins/cloud/models.py:32 +msgid "Provider" +msgstr "" + +#: xpack/plugins/cloud/models.py:36 +msgid "Validity" +msgstr "" + +#: xpack/plugins/cloud/models.py:41 +msgid "Cloud account" +msgstr "" + +#: xpack/plugins/cloud/models.py:43 +msgid "Test cloud account" +msgstr "" + +#: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:38 +msgid "Regions" +msgstr "" + +#: xpack/plugins/cloud/models.py:93 +msgid "Hostname strategy" +msgstr "" + +#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:72 +msgid "Unix admin user" +msgstr "" + +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:73 +msgid "Windows admin user" +msgstr "" + +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:46 +msgid "IP network segment group" +msgstr "" + +#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:51 +msgid "Sync IP type" +msgstr "" + +#: xpack/plugins/cloud/models.py:118 xpack/plugins/cloud/serializers/task.py:76 +msgid "Always update" +msgstr "" + +#: xpack/plugins/cloud/models.py:124 +msgid "Date last sync" +msgstr "" + +#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +msgid "Sync instance task" +msgstr "" + +#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +msgid "Date sync" +msgstr "" + +#: xpack/plugins/cloud/models.py:185 +msgid "Sync instance task execution" +msgstr "" + +#: xpack/plugins/cloud/models.py:209 +msgid "Sync task" +msgstr "" + +#: xpack/plugins/cloud/models.py:213 +msgid "Sync instance task history" +msgstr "" + +#: xpack/plugins/cloud/models.py:216 +msgid "Instance" +msgstr "" + +#: xpack/plugins/cloud/models.py:233 +msgid "Sync instance detail" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:17 +msgid "China (Beijing)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:18 +msgid "China (Ningxia)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:21 +msgid "US East (Ohio)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:22 +msgid "US East (N. Virginia)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:23 +msgid "US West (N. California)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:24 +msgid "US West (Oregon)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:25 +msgid "Africa (Cape Town)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:26 +msgid "Asia Pacific (Hong Kong)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:27 +msgid "Asia Pacific (Mumbai)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:28 +msgid "Asia Pacific (Osaka-Local)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:29 +msgid "Asia Pacific (Seoul)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:30 +msgid "Asia Pacific (Singapore)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:31 +msgid "Asia Pacific (Sydney)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:32 +msgid "Asia Pacific (Tokyo)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:33 +msgid "Canada (Central)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:34 +msgid "Europe (Frankfurt)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:35 +msgid "Europe (Ireland)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:36 +msgid "Europe (London)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:37 +msgid "Europe (Milan)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:38 +msgid "Europe (Paris)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:39 +msgid "Europe (Stockholm)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:40 +msgid "Middle East (Bahrain)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:41 +msgid "South America (São Paulo)" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 +msgid "CN North-Beijing" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:55 +#: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 +msgid "CN South-Guangzhou" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:56 +msgid "CN East-Suzhou" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:57 +#: xpack/plugins/cloud/providers/huaweicloud.py:48 +msgid "CN-Hong Kong" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:58 +msgid "CN Center-Wuhan" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:59 +msgid "CN North-Baoding" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 +msgid "CN East-Shanghai" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:61 +#: xpack/plugins/cloud/providers/huaweicloud.py:47 +msgid "AP-Singapore" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:35 +msgid "AF-Johannesburg" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:36 +msgid "CN North-Beijing4" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:37 +msgid "CN North-Beijing1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:38 +msgid "CN East-Shanghai2" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:39 +msgid "CN East-Shanghai1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:41 +msgid "LA-Mexico City1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:42 +msgid "LA-Santiago" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:43 +msgid "LA-Sao Paulo1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:44 +msgid "EU-Paris" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:45 +msgid "CN Southwest-Guiyang1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:46 +msgid "AP-Bangkok" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:50 +msgid "CN Northeast-Dalian" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:51 +msgid "CN North-Ulanqab1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:52 +msgid "CN South-Guangzhou-InvitationOnly" +msgstr "" + +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "" + +#: xpack/plugins/cloud/serializers/account.py:65 +msgid "Validity display" +msgstr "" + +#: xpack/plugins/cloud/serializers/account.py:66 +msgid "Provider display" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:35 +msgid "Client ID" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:41 +msgid "Tenant ID" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:44 +msgid "Subscription ID" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:95 +#: xpack/plugins/cloud/serializers/account_attrs.py:100 +#: xpack/plugins/cloud/serializers/account_attrs.py:116 +#: xpack/plugins/cloud/serializers/account_attrs.py:141 +msgid "API Endpoint" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:106 +msgid "Auth url" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:107 +msgid "eg: http://openstack.example.com:5000/v3" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:110 +msgid "User domain" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:117 +msgid "Cert File" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:118 +msgid "Key File" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:134 +msgid "Service account key" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:135 +msgid "The file is in JSON format" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:148 +msgid "IP address invalid `{}`, {}" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:154 +msgid "" +"Format for comma-delimited string,Such as: 192.168.1.0/24, " +"10.0.0.0-10.0.0.255" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:158 +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 " +"synchronized.
If the port is 0, all IP addresses are valid." +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:166 +msgid "Hostname prefix" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:169 +msgid "IP segment" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:173 +msgid "Test port" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:176 +msgid "Test timeout" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:29 +msgid "" +"Only instances matching the IP range will be synced.
If the instance " +"contains multiple IP addresses, the first IP address that matches will be " +"used as the IP for the created asset.
The default value of * means sync " +"all instances and randomly match IP addresses.
Format for comma-" +"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:36 +msgid "History count" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:37 +msgid "Instance count" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:70 +msgid "Linux admin user" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:75 +#: xpack/plugins/gathered_user/serializers.py:20 +msgid "Periodic display" +msgstr "" + +#: xpack/plugins/cloud/utils.py:69 +msgid "Account unavailable" +msgstr "" + +#: xpack/plugins/gathered_user/meta.py:11 +msgid "Gathered user" +msgstr "" + +#: xpack/plugins/gathered_user/models.py:34 +msgid "Gather user task" +msgstr "" + +#: xpack/plugins/gathered_user/models.py:80 +msgid "gather user task execution" +msgstr "" + +#: xpack/plugins/gathered_user/models.py:86 +msgid "Assets is empty, please change nodes" +msgstr "" + +#: xpack/plugins/gathered_user/serializers.py:21 +msgid "Executed times" +msgstr "" + +#: xpack/plugins/interface/api.py:52 +msgid "Restore default successfully." +msgstr "" + +#: xpack/plugins/interface/meta.py:10 +msgid "Interface settings" +msgstr "" + +#: xpack/plugins/interface/models.py:22 +msgid "Title of login page" +msgstr "" + +#: xpack/plugins/interface/models.py:26 +msgid "Image of login page" +msgstr "" + +#: xpack/plugins/interface/models.py:30 +msgid "Website icon" +msgstr "" + +#: xpack/plugins/interface/models.py:34 +msgid "Logo of management page" +msgstr "" + +#: xpack/plugins/interface/models.py:38 +msgid "Logo of logout page" +msgstr "" + +#: xpack/plugins/interface/models.py:40 +msgid "Theme" +msgstr "" + +#: xpack/plugins/interface/models.py:43 xpack/plugins/interface/models.py:84 +msgid "Interface setting" +msgstr "" + +#: xpack/plugins/license/api.py:50 +msgid "License import successfully" +msgstr "" + +#: xpack/plugins/license/api.py:51 +msgid "License is invalid" +msgstr "" + +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:127 +msgid "License" +msgstr "" + +#: xpack/plugins/license/models.py:71 +msgid "Standard edition" +msgstr "" + +#: xpack/plugins/license/models.py:73 +msgid "Enterprise edition" +msgstr "" + +#: xpack/plugins/license/models.py:75 +msgid "Ultimate edition" +msgstr "" + +#: xpack/plugins/license/models.py:77 +msgid "Community edition" +msgstr "" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 0ff05dd98..19eccde31 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:1c09abdddb5699aeaf832e1162b58ea9b520c10df3f80390c0ec680da3e18f4d -size 103641 +oid sha256:6c0ba1103efe746ecf579fe27832b5d2969858508f4aabdcc42723b13c1b01f8 +size 383 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po new file mode 100644 index 000000000..63a82105c --- /dev/null +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -0,0 +1,6995 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-06 17:33+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: acls/apps.py:7 +msgid "Acls" +msgstr "" + +#: acls/models/base.py:20 tickets/const.py:45 +#: tickets/templates/tickets/approve_check_password.html:49 +msgid "Reject" +msgstr "" + +#: acls/models/base.py:21 +msgid "Accept" +msgstr "" + +#: acls/models/base.py:22 +msgid "Review" +msgstr "" + +#: acls/models/base.py:71 acls/models/command_acl.py:22 +#: acls/serializers/base.py:34 applications/models.py:10 +#: assets/models/_user.py:33 assets/models/asset/common.py:81 +#: assets/models/asset/common.py:91 assets/models/base.py:64 +#: assets/models/cmd_filter.py:26 assets/models/domain.py:21 +#: assets/models/group.py:20 assets/models/label.py:17 +#: assets/models/platform.py:21 assets/models/platform.py:72 +#: assets/serializers/asset/common.py:87 assets/serializers/platform.py:139 +#: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 +#: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 +#: perms/models/asset_permission.py:51 rbac/models/role.py:29 +#: settings/models.py:33 settings/serializers/sms.py:6 +#: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:12 +#: terminal/models/component/endpoint.py:86 +#: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 +#: terminal/models/component/terminal.py:80 users/forms/profile.py:33 +#: users/models/group.py:15 users/models/user.py:675 +#: xpack/plugins/cloud/models.py:30 +msgid "Name" +msgstr "" + +#: acls/models/base.py:73 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:89 +msgid "Priority" +msgstr "" + +#: acls/models/base.py:74 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:90 +msgid "1-100, the lower the value will be match first" +msgstr "" + +#: acls/models/base.py:77 acls/serializers/base.py:63 +#: assets/models/cmd_filter.py:86 audits/models.py:51 audits/serializers.py:75 +#: authentication/templates/authentication/_access_key_modal.html:34 +msgid "Action" +msgstr "" + +#: acls/models/base.py:78 acls/serializers/base.py:59 +#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:91 +#: authentication/serializers/connect_token_secret.py:79 +msgid "Reviewers" +msgstr "" + +#: acls/models/base.py:79 authentication/models/access_key.py:17 +#: authentication/templates/authentication/_access_key_modal.html:32 +#: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 +#: tickets/const.py:37 +msgid "Active" +msgstr "" + +#: acls/models/base.py:80 acls/models/command_acl.py:29 +#: applications/models.py:19 assets/models/_user.py:40 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:22 +#: assets/models/backup.py:29 assets/models/base.py:72 +#: assets/models/cmd_filter.py:45 assets/models/cmd_filter.py:93 +#: assets/models/domain.py:22 assets/models/group.py:23 +#: assets/models/label.py:22 assets/models/platform.py:77 +#: ops/models/adhoc.py:27 ops/models/job.py:50 ops/models/playbook.py:17 +#: orgs/models.py:74 perms/models/asset_permission.py:71 rbac/models/role.py:37 +#: settings/models.py:38 terminal/models/applet/applet.py:28 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 +#: terminal/models/component/endpoint.py:20 +#: terminal/models/component/endpoint.py:96 +#: terminal/models/component/storage.py:28 +#: terminal/models/component/terminal.py:92 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:296 users/models/group.py:16 +#: users/models/user.py:714 xpack/plugins/change_auth_plan/models/base.py:44 +#: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:121 +#: xpack/plugins/gathered_user/models.py:26 +msgid "Comment" +msgstr "" + +#: acls/models/base.py:92 acls/models/login_acl.py:13 +#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 +#: assets/models/cmd_filter.py:29 assets/models/label.py:15 audits/models.py:30 +#: audits/models.py:49 audits/models.py:93 +#: authentication/models/connection_token.py:25 +#: authentication/models/sso_token.py:16 +#: notifications/models/notification.py:12 +#: perms/api/user_permission/mixin.py:69 perms/models/asset_permission.py:53 +#: perms/models/perm_token.py:12 rbac/builtin.py:120 +#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20 +#: terminal/backends/command/serializers.py:13 +#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 +#: terminal/notifications.py:94 terminal/notifications.py:142 +#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:907 +#: users/models/user.py:938 users/serializers/group.py:19 +msgid "User" +msgstr "" + +#: acls/models/base.py:94 acls/serializers/base.py:56 +#: assets/models/account.py:51 assets/models/asset/common.py:83 +#: assets/models/asset/common.py:212 assets/models/cmd_filter.py:41 +#: assets/models/gathered_user.py:14 assets/serializers/account/account.py:59 +#: assets/serializers/automations/change_secret.py:100 +#: assets/serializers/automations/change_secret.py:122 +#: assets/serializers/domain.py:19 assets/serializers/gathered_user.py:11 +#: assets/serializers/label.py:30 audits/models.py:34 +#: authentication/models/connection_token.py:29 +#: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 +#: terminal/backends/command/models.py:21 +#: terminal/backends/command/serializers.py:14 +#: terminal/models/session/session.py:32 terminal/notifications.py:93 +#: xpack/plugins/change_auth_plan/models/asset.py:200 +#: xpack/plugins/change_auth_plan/serializers/asset.py:172 +#: xpack/plugins/cloud/models.py:222 +msgid "Asset" +msgstr "" + +#: acls/models/base.py:96 acls/serializers/base.py:57 +#: assets/models/account.py:61 +#: assets/serializers/automations/change_secret.py:101 +#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 +#: perms/models/perm_token.py:14 terminal/backends/command/models.py:22 +#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 +#: xpack/plugins/cloud/serializers/task.py:71 +msgid "Account" +msgstr "" + +#: acls/models/command_acl.py:17 assets/models/cmd_filter.py:65 +#: terminal/backends/command/serializers.py:15 +#: terminal/models/session/session.py:41 +#: terminal/templates/terminal/_msg_command_alert.html:12 +#: terminal/templates/terminal/_msg_command_execute_alert.html:10 +msgid "Command" +msgstr "" + +#: acls/models/command_acl.py:18 assets/models/cmd_filter.py:64 +msgid "Regex" +msgstr "" + +#: acls/models/command_acl.py:25 acls/serializers/command_acl.py:14 +#: applications/models.py:15 assets/models/_user.py:46 +#: assets/models/automations/base.py:20 assets/models/cmd_filter.py:79 +#: assets/models/platform.py:74 assets/serializers/asset/common.py:63 +#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:99 +#: audits/serializers.py:40 ops/models/job.py:42 +#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 +#: terminal/models/component/storage.py:57 +#: terminal/models/component/storage.py:146 terminal/serializers/applet.py:33 +#: tickets/models/comment.py:26 tickets/models/flow.py:57 +#: tickets/models/ticket/apply_application.py:16 +#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 +#: tickets/serializers/ticket/ticket.py:19 +#: xpack/plugins/change_auth_plan/models/app.py:27 +#: xpack/plugins/change_auth_plan/models/app.py:152 +msgid "Type" +msgstr "" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 +msgid "Content" +msgstr "" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 +msgid "One line one command" +msgstr "" + +#: acls/models/command_acl.py:28 assets/models/cmd_filter.py:85 +msgid "Ignore case" +msgstr "" + +#: acls/models/command_acl.py:35 acls/serializers/command_acl.py:24 +#: authentication/serializers/connect_token_secret.py:76 +msgid "Command group" +msgstr "" + +#: acls/models/command_acl.py:88 +msgid "The generated regular expression is incorrect: {}" +msgstr "" + +#: acls/models/command_acl.py:98 +msgid "Commands" +msgstr "" + +#: acls/models/command_acl.py:102 +msgid "Command acl" +msgstr "" + +#: acls/models/command_acl.py:111 tickets/const.py:11 +msgid "Command confirm" +msgstr "" + +#: acls/models/login_acl.py:16 +msgid "Rule" +msgstr "" + +#: acls/models/login_acl.py:19 +msgid "Login acl" +msgstr "" + +#: acls/models/login_acl.py:54 tickets/const.py:10 +msgid "Login confirm" +msgstr "" + +#: acls/models/login_asset_acl.py:10 +msgid "Login asset acl" +msgstr "" + +#: acls/models/login_asset_acl.py:20 tickets/const.py:12 +msgid "Login asset confirm" +msgstr "" + +#: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 +msgid "Format for comma-delimited string, with * indicating a match all. " +msgstr "" + +#: acls/serializers/base.py:18 acls/serializers/base.py:49 +#: assets/models/_user.py:34 assets/models/base.py:65 +#: assets/models/gathered_user.py:15 audits/models.py:109 +#: authentication/forms.py:25 authentication/forms.py:27 +#: authentication/models/temp_token.py:9 +#: authentication/templates/authentication/_msg_different_city.html:9 +#: authentication/templates/authentication/_msg_oauth_bind.html:9 +#: users/forms/profile.py:32 users/forms/profile.py:112 +#: users/models/user.py:673 users/templates/users/_msg_user_created.html:12 +#: xpack/plugins/change_auth_plan/models/asset.py:35 +#: xpack/plugins/change_auth_plan/models/asset.py:196 +#: xpack/plugins/cloud/serializers/account_attrs.py:26 +msgid "Username" +msgstr "" + +#: acls/serializers/base.py:25 +msgid "" +"Format for comma-delimited string, with * indicating a match all. Such as: " +"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" +"db8:1a:1110::/64 (Domain name support)" +msgstr "" + +#: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 +msgid "IP/Host" +msgstr "" + +#: acls/serializers/base.py:90 tickets/serializers/ticket/ticket.py:79 +msgid "The organization `{}` does not exist" +msgstr "" + +#: acls/serializers/base.py:96 +msgid "None of the reviewers belong to Organization `{}`" +msgstr "" + +#: acls/serializers/rules/rules.py:20 +#: xpack/plugins/cloud/serializers/task.py:23 +msgid "IP address invalid: `{}`" +msgstr "" + +#: acls/serializers/rules/rules.py:25 +msgid "" +"Format for comma-delimited string, with * indicating a match all. Such as: " +"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" +"db8:1a:1110::/64 " +msgstr "" + +#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 +#: 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:54 +msgid "IP" +msgstr "" + +#: acls/serializers/rules/rules.py:35 +msgid "Time Period" +msgstr "" + +#: applications/apps.py:9 +msgid "Applications" +msgstr "" + +#: applications/models.py:12 assets/models/label.py:20 +#: assets/models/platform.py:73 assets/serializers/asset/common.py:62 +#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:100 +#: assets/serializers/platform.py:140 perms/serializers/user_permission.py:23 +#: settings/models.py:35 tickets/models/ticket/apply_application.py:13 +#: xpack/plugins/change_auth_plan/models/app.py:24 +msgid "Category" +msgstr "" + +#: applications/models.py:17 xpack/plugins/cloud/models.py:35 +#: xpack/plugins/cloud/serializers/account.py:64 +msgid "Attrs" +msgstr "" + +#: applications/models.py:23 xpack/plugins/change_auth_plan/models/app.py:31 +msgid "Application" +msgstr "" + +#: applications/models.py:27 +msgid "Can match application" +msgstr "" + +#: applications/serializers/attrs/application_type/clickhouse.py:11 +#: assets/models/asset/common.py:82 assets/models/platform.py:22 +#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 +#: xpack/plugins/cloud/serializers/account_attrs.py:73 +msgid "Port" +msgstr "" + +#: applications/serializers/attrs/application_type/clickhouse.py:13 +msgid "" +"Typically, the port is 9000,the HTTP interface and the native interface use " +"different ports" +msgstr "" + +#: assets/api/automations/base.py:76 +#: xpack/plugins/change_auth_plan/api/asset.py:94 +msgid "The parameter 'action' must be [{}]" +msgstr "" + +#: assets/api/domain.py:56 +msgid "Number required" +msgstr "" + +#: assets/api/node.py:62 +msgid "You can't update the root node name" +msgstr "" + +#: assets/api/node.py:69 +msgid "You can't delete the root node ({})" +msgstr "" + +#: assets/api/node.py:72 +msgid "Deletion failed and the node contains assets" +msgstr "" + +#: assets/apps.py:9 +msgid "App assets" +msgstr "" + +#: assets/automations/base/manager.py:123 +msgid "{} disabled" +msgstr "" + +#: assets/const/account.py:6 audits/const.py:6 audits/const.py:64 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 +msgid "Unknown" +msgstr "" + +#: assets/const/account.py:7 +msgid "Ok" +msgstr "" + +#: assets/const/account.py:8 +#: assets/serializers/automations/change_secret.py:118 +#: assets/serializers/automations/change_secret.py:146 audits/const.py:75 +#: common/const/choices.py:19 +#: xpack/plugins/change_auth_plan/serializers/asset.py:190 +#: xpack/plugins/cloud/const.py:41 +msgid "Failed" +msgstr "" + +#: assets/const/account.py:12 assets/models/_user.py:35 +#: audits/signal_handlers.py:49 authentication/confirm/password.py:9 +#: authentication/forms.py:32 +#: authentication/templates/authentication/login.html:228 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 +#: users/forms/profile.py:22 users/serializers/user.py:105 +#: users/templates/users/_msg_user_created.html:13 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models/base.py:42 +#: xpack/plugins/change_auth_plan/models/base.py:117 +#: xpack/plugins/change_auth_plan/models/base.py:192 +#: xpack/plugins/change_auth_plan/serializers/base.py:21 +#: xpack/plugins/change_auth_plan/serializers/base.py:73 +#: xpack/plugins/cloud/serializers/account_attrs.py:28 +msgid "Password" +msgstr "" + +#: assets/const/account.py:13 +msgid "SSH key" +msgstr "" + +#: assets/const/account.py:14 authentication/models/access_key.py:33 +msgid "Access key" +msgstr "" + +#: assets/const/account.py:15 assets/models/_user.py:38 +#: authentication/models/sso_token.py:14 +msgid "Token" +msgstr "" + +#: assets/const/automation.py:13 +msgid "Ping" +msgstr "" + +#: assets/const/automation.py:14 +msgid "Gather facts" +msgstr "" + +#: assets/const/automation.py:15 +msgid "Create account" +msgstr "" + +#: assets/const/automation.py:16 +msgid "Change secret" +msgstr "" + +#: assets/const/automation.py:17 +msgid "Verify account" +msgstr "" + +#: assets/const/automation.py:18 +msgid "Gather accounts" +msgstr "" + +#: assets/const/automation.py:38 assets/serializers/account/base.py:26 +msgid "Specific" +msgstr "" + +#: assets/const/automation.py:39 ops/const.py:20 +#: xpack/plugins/change_auth_plan/models/base.py:28 +msgid "All assets use the same random password" +msgstr "" + +#: assets/const/automation.py:40 ops/const.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:29 +msgid "All assets use different random password" +msgstr "" + +#: assets/const/automation.py:44 ops/const.py:13 +#: xpack/plugins/change_auth_plan/models/asset.py:30 +msgid "Append SSH KEY" +msgstr "" + +#: assets/const/automation.py:45 ops/const.py:14 +#: xpack/plugins/change_auth_plan/models/asset.py:31 +msgid "Empty and append SSH KEY" +msgstr "" + +#: assets/const/automation.py:46 ops/const.py:15 +#: xpack/plugins/change_auth_plan/models/asset.py:32 +msgid "Replace (The key generated by JumpServer) " +msgstr "" + +#: assets/const/category.py:11 settings/serializers/auth/radius.py:16 +#: settings/serializers/auth/sms.py:67 terminal/models/applet/applet.py:59 +#: terminal/models/component/endpoint.py:13 +#: xpack/plugins/cloud/serializers/account_attrs.py:72 +msgid "Host" +msgstr "" + +#: assets/const/category.py:12 +msgid "Device" +msgstr "" + +#: assets/const/category.py:13 assets/models/asset/database.py:8 +#: assets/models/asset/database.py:34 +msgid "Database" +msgstr "" + +#: assets/const/category.py:14 +msgid "Cloud service" +msgstr "" + +#: assets/const/category.py:15 audits/const.py:62 +#: terminal/models/applet/applet.py:18 +msgid "Web" +msgstr "" + +#: assets/const/device.py:7 terminal/models/applet/applet.py:17 +#: tickets/const.py:8 +msgid "General" +msgstr "" + +#: assets/const/device.py:8 +msgid "Switch" +msgstr "" + +#: assets/const/device.py:9 +msgid "Router" +msgstr "" + +#: assets/const/device.py:10 +msgid "Firewall" +msgstr "" + +#: assets/const/web.py:7 +msgid "Website" +msgstr "" + +#: assets/models/_user.py:24 +msgid "Automatic managed" +msgstr "" + +#: assets/models/_user.py:25 +msgid "Manually input" +msgstr "" + +#: assets/models/_user.py:29 +msgid "Common user" +msgstr "" + +#: assets/models/_user.py:30 +msgid "Admin user" +msgstr "" + +#: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 +#: xpack/plugins/change_auth_plan/models/asset.py:131 +#: xpack/plugins/change_auth_plan/models/asset.py:207 +msgid "SSH private key" +msgstr "" + +#: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 +#: xpack/plugins/change_auth_plan/models/asset.py:127 +#: xpack/plugins/change_auth_plan/models/asset.py:203 +msgid "SSH public key" +msgstr "" + +#: assets/models/_user.py:41 assets/models/automations/base.py:92 +#: assets/models/cmd_filter.py:46 assets/models/domain.py:23 +#: assets/models/gathered_user.py:19 assets/models/group.py:22 +#: common/db/models.py:77 common/mixins/models.py:50 ops/models/base.py:54 +#: ops/models/job.py:108 orgs/models.py:73 perms/models/asset_permission.py:74 +#: users/models/group.py:18 users/models/user.py:939 +#: xpack/plugins/change_auth_plan/models/base.py:45 +msgid "Date created" +msgstr "" + +#: assets/models/_user.py:42 assets/models/cmd_filter.py:47 +#: assets/models/gathered_user.py:20 common/db/models.py:78 +#: common/mixins/models.py:51 xpack/plugins/change_auth_plan/models/base.py:46 +msgid "Date updated" +msgstr "" + +#: assets/models/_user.py:43 assets/models/base.py:73 +#: assets/models/cmd_filter.py:49 assets/models/cmd_filter.py:96 +#: assets/models/group.py:21 common/db/models.py:75 common/mixins/models.py:49 +#: orgs/models.py:71 perms/models/asset_permission.py:75 +#: users/models/user.py:722 users/serializers/group.py:33 +#: xpack/plugins/change_auth_plan/models/base.py:48 +msgid "Created by" +msgstr "" + +#: assets/models/_user.py:45 +msgid "Username same with user" +msgstr "" + +#: assets/models/_user.py:48 authentication/models/connection_token.py:34 +#: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 +#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 +#: terminal/serializers/storage.py:68 +msgid "Protocol" +msgstr "" + +#: assets/models/_user.py:49 +msgid "Auto push" +msgstr "" + +#: assets/models/_user.py:50 +msgid "Sudo" +msgstr "" + +#: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 +msgid "Shell" +msgstr "" + +#: assets/models/_user.py:52 +msgid "Login mode" +msgstr "" + +#: assets/models/_user.py:53 +msgid "SFTP Root" +msgstr "" + +#: assets/models/_user.py:54 +msgid "Home" +msgstr "" + +#: assets/models/_user.py:55 +msgid "System groups" +msgstr "" + +#: assets/models/_user.py:58 +msgid "User switch" +msgstr "" + +#: assets/models/_user.py:59 +msgid "Switch from" +msgstr "" + +#: assets/models/_user.py:65 audits/models.py:35 +#: xpack/plugins/change_auth_plan/models/app.py:35 +#: xpack/plugins/change_auth_plan/models/app.py:146 +msgid "System user" +msgstr "" + +#: assets/models/_user.py:67 +msgid "Can match system user" +msgstr "" + +#: assets/models/account.py:45 common/db/fields.py:232 +#: settings/serializers/terminal.py:14 +msgid "All" +msgstr "" + +#: assets/models/account.py:46 +msgid "Manual input" +msgstr "" + +#: assets/models/account.py:47 +msgid "Dynamic user" +msgstr "" + +#: assets/models/account.py:55 +#: authentication/serializers/connect_token_secret.py:47 +msgid "Su from" +msgstr "" + +#: assets/models/account.py:57 settings/serializers/auth/cas.py:20 +#: terminal/models/applet/applet.py:22 +msgid "Version" +msgstr "" + +#: assets/models/account.py:67 +msgid "Can view asset account secret" +msgstr "" + +#: assets/models/account.py:68 +msgid "Can change asset account secret" +msgstr "" + +#: assets/models/account.py:69 +msgid "Can view asset history account" +msgstr "" + +#: assets/models/account.py:70 +msgid "Can view asset history account secret" +msgstr "" + +#: assets/models/account.py:93 assets/serializers/account/account.py:15 +msgid "Account template" +msgstr "" + +#: assets/models/account.py:98 +msgid "Can view asset account template secret" +msgstr "" + +#: assets/models/account.py:99 +msgid "Can change asset account template secret" +msgstr "" + +#: assets/models/asset/common.py:93 assets/models/platform.py:110 +#: assets/serializers/asset/common.py:65 +#: perms/serializers/user_permission.py:21 +#: xpack/plugins/cloud/serializers/account_attrs.py:179 +msgid "Platform" +msgstr "" + +#: assets/models/asset/common.py:95 assets/models/domain.py:26 +#: assets/serializers/asset/common.py:64 +#: authentication/serializers/connect_token_secret.py:105 +msgid "Domain" +msgstr "" + +#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 +#: assets/models/cmd_filter.py:37 assets/serializers/asset/common.py:66 +#: assets/serializers/automations/base.py:21 +#: perms/models/asset_permission.py:62 +#: xpack/plugins/change_auth_plan/models/asset.py:44 +#: xpack/plugins/gathered_user/models.py:24 +msgid "Nodes" +msgstr "" + +#: assets/models/asset/common.py:98 assets/models/automations/base.py:21 +#: assets/models/base.py:71 assets/models/cmd_filter.py:44 +#: assets/models/label.py:21 terminal/models/applet/applet.py:25 +#: users/serializers/user.py:202 +msgid "Is active" +msgstr "" + +#: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 +msgid "Labels" +msgstr "" + +#: assets/models/asset/common.py:215 +msgid "Can refresh asset hardware info" +msgstr "" + +#: assets/models/asset/common.py:216 +msgid "Can test asset connectivity" +msgstr "" + +#: assets/models/asset/common.py:217 +msgid "Can push account to asset" +msgstr "" + +#: assets/models/asset/common.py:218 +msgid "Can match asset" +msgstr "" + +#: assets/models/asset/common.py:219 +msgid "Add asset to node" +msgstr "" + +#: assets/models/asset/common.py:220 +msgid "Move asset to node" +msgstr "" + +#: assets/models/asset/database.py:9 settings/serializers/email.py:37 +msgid "Use SSL" +msgstr "" + +#: assets/models/asset/database.py:10 +msgid "CA cert" +msgstr "" + +#: assets/models/asset/database.py:11 +msgid "Client cert" +msgstr "" + +#: assets/models/asset/database.py:12 +msgid "Client key" +msgstr "" + +#: assets/models/asset/database.py:13 +msgid "Allow invalid cert" +msgstr "" + +#: assets/models/asset/web.py:9 audits/const.py:68 +#: terminal/serializers/applet_host.py:25 +msgid "Disabled" +msgstr "" + +#: assets/models/asset/web.py:10 settings/serializers/auth/base.py:10 +#: settings/serializers/basic.py:27 +msgid "Basic" +msgstr "" + +#: assets/models/asset/web.py:11 assets/models/asset/web.py:17 +msgid "Script" +msgstr "" + +#: assets/models/asset/web.py:13 +msgid "Autofill" +msgstr "" + +#: assets/models/asset/web.py:14 assets/serializers/platform.py:30 +msgid "Username selector" +msgstr "" + +#: assets/models/asset/web.py:15 assets/serializers/platform.py:33 +msgid "Password selector" +msgstr "" + +#: assets/models/asset/web.py:16 assets/serializers/platform.py:36 +msgid "Submit selector" +msgstr "" + +#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:43 +#: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 +#: perms/serializers/permission.py:32 rbac/tree.py:37 +msgid "Accounts" +msgstr "" + +#: assets/models/automations/base.py:19 +#: assets/serializers/automations/base.py:20 ops/models/base.py:17 +#: ops/models/job.py:44 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 +#: xpack/plugins/change_auth_plan/models/asset.py:40 +msgid "Assets" +msgstr "" + +#: assets/models/automations/base.py:82 assets/models/automations/base.py:89 +msgid "Automation task" +msgstr "" + +#: assets/models/automations/base.py:91 audits/models.py:129 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 +#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 +#: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 +#: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:20 +#: xpack/plugins/cloud/models.py:174 xpack/plugins/cloud/models.py:226 +msgid "Status" +msgstr "" + +#: assets/models/automations/base.py:93 assets/models/backup.py:76 +#: audits/models.py:41 ops/models/base.py:55 ops/models/celery.py:59 +#: ops/models/job.py:109 perms/models/asset_permission.py:67 +#: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 +#: tickets/models/ticket/apply_application.py:30 +#: tickets/models/ticket/apply_asset.py:19 +#: xpack/plugins/change_auth_plan/models/base.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:199 +#: xpack/plugins/gathered_user/models.py:71 +msgid "Date start" +msgstr "" + +#: assets/models/automations/base.py:94 +#: assets/models/automations/change_secret.py:59 ops/models/base.py:56 +#: ops/models/celery.py:60 ops/models/job.py:110 +#: terminal/models/applet/host.py:106 +msgid "Date finished" +msgstr "" + +#: assets/models/automations/base.py:96 +#: assets/serializers/automations/base.py:39 +msgid "Automation snapshot" +msgstr "" + +#: assets/models/automations/base.py:100 assets/models/backup.py:87 +#: assets/serializers/account/backup.py:37 +#: assets/serializers/automations/base.py:41 +#: xpack/plugins/change_auth_plan/models/base.py:121 +#: xpack/plugins/change_auth_plan/serializers/base.py:78 +msgid "Trigger mode" +msgstr "" + +#: assets/models/automations/base.py:104 +#: assets/serializers/automations/change_secret.py:103 +msgid "Automation task execution" +msgstr "" + +#: assets/models/automations/change_secret.py:15 assets/models/base.py:67 +#: assets/serializers/account/account.py:97 assets/serializers/base.py:13 +msgid "Secret type" +msgstr "" + +#: assets/models/automations/change_secret.py:19 +#: assets/serializers/automations/change_secret.py:25 +msgid "Secret strategy" +msgstr "" + +#: assets/models/automations/change_secret.py:21 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:69 +#: assets/serializers/base.py:16 authentication/models/temp_token.py:10 +#: authentication/templates/authentication/_access_key_modal.html:31 +#: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:19 +msgid "Secret" +msgstr "" + +#: assets/models/automations/change_secret.py:22 +#: xpack/plugins/change_auth_plan/models/base.py:39 +msgid "Password rules" +msgstr "" + +#: assets/models/automations/change_secret.py:25 +msgid "SSH key change strategy" +msgstr "" + +#: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 +#: assets/serializers/account/backup.py:30 +#: assets/serializers/automations/change_secret.py:40 +#: xpack/plugins/change_auth_plan/models/app.py:40 +#: xpack/plugins/change_auth_plan/models/asset.py:63 +#: xpack/plugins/change_auth_plan/serializers/base.py:45 +msgid "Recipient" +msgstr "" + +#: assets/models/automations/change_secret.py:34 +msgid "Change secret automation" +msgstr "" + +#: assets/models/automations/change_secret.py:56 +msgid "Old secret" +msgstr "" + +#: assets/models/automations/change_secret.py:58 +msgid "Date started" +msgstr "" + +#: assets/models/automations/change_secret.py:61 common/const/choices.py:20 +msgid "Error" +msgstr "" + +#: assets/models/automations/change_secret.py:64 +msgid "Change secret record" +msgstr "" + +#: assets/models/automations/discovery_account.py:8 +msgid "Discovery account automation" +msgstr "" + +#: assets/models/automations/gather_accounts.py:15 +#: assets/tasks/gather_accounts.py:28 +msgid "Gather asset accounts" +msgstr "" + +#: assets/models/automations/gather_facts.py:15 +msgid "Gather asset facts" +msgstr "" + +#: assets/models/automations/ping.py:15 +msgid "Ping asset" +msgstr "" + +#: assets/models/automations/push_account.py:16 +msgid "Push asset account" +msgstr "" + +#: assets/models/automations/verify_account.py:15 +msgid "Verify asset account" +msgstr "" + +#: assets/models/backup.py:37 assets/models/backup.py:95 +msgid "Account backup plan" +msgstr "" + +#: assets/models/backup.py:79 +#: authentication/templates/authentication/_msg_oauth_bind.html:11 +#: notifications/notifications.py:186 +#: xpack/plugins/change_auth_plan/models/base.py:111 +#: xpack/plugins/change_auth_plan/models/base.py:200 +#: xpack/plugins/gathered_user/models.py:74 +msgid "Time" +msgstr "" + +#: assets/models/backup.py:83 +msgid "Account backup snapshot" +msgstr "" + +#: assets/models/backup.py:90 audits/models.py:124 +#: terminal/models/session/sharing.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:197 +#: xpack/plugins/change_auth_plan/serializers/asset.py:171 +#: xpack/plugins/cloud/models.py:178 +msgid "Reason" +msgstr "" + +#: assets/models/backup.py:92 +#: assets/serializers/automations/change_secret.py:99 +#: assets/serializers/automations/change_secret.py:124 +#: terminal/serializers/session.py:36 +#: xpack/plugins/change_auth_plan/models/base.py:198 +#: xpack/plugins/change_auth_plan/serializers/asset.py:173 +msgid "Is success" +msgstr "" + +#: assets/models/backup.py:99 +msgid "Account backup execution" +msgstr "" + +#: assets/models/base.py:26 +msgid "Connectivity" +msgstr "" + +#: assets/models/base.py:28 authentication/models/temp_token.py:12 +msgid "Date verified" +msgstr "" + +#: assets/models/base.py:70 +msgid "Privileged" +msgstr "" + +#: assets/models/cmd_filter.py:33 perms/models/asset_permission.py:56 +#: users/models/group.py:31 users/models/user.py:681 +msgid "User group" +msgstr "" + +#: assets/models/cmd_filter.py:57 +msgid "Command filter" +msgstr "" + +#: assets/models/cmd_filter.py:71 +msgid "Deny" +msgstr "" + +#: assets/models/cmd_filter.py:72 +msgid "Allow" +msgstr "" + +#: assets/models/cmd_filter.py:73 +msgid "Reconfirm" +msgstr "" + +#: assets/models/cmd_filter.py:77 +msgid "Filter" +msgstr "" + +#: assets/models/cmd_filter.py:100 +msgid "Command filter rule" +msgstr "" + +#: assets/models/gateway.py:61 authentication/models/connection_token.py:101 +msgid "No account" +msgstr "" + +#: assets/models/gateway.py:83 +#, python-brace-format +msgid "Unable to connect to port {port} on {address}" +msgstr "" + +#: assets/models/gateway.py:86 authentication/middleware.py:76 +#: xpack/plugins/cloud/providers/fc.py:48 +msgid "Authentication failed" +msgstr "" + +#: assets/models/gateway.py:88 assets/models/gateway.py:115 +msgid "Connect failed" +msgstr "" + +#: assets/models/gathered_user.py:16 +msgid "Present" +msgstr "" + +#: assets/models/gathered_user.py:17 +msgid "Date last login" +msgstr "" + +#: assets/models/gathered_user.py:18 +msgid "IP last login" +msgstr "" + +#: assets/models/gathered_user.py:31 +msgid "GatherUser" +msgstr "" + +#: assets/models/group.py:30 +msgid "Asset group" +msgstr "" + +#: assets/models/group.py:34 assets/models/platform.py:19 +#: xpack/plugins/cloud/providers/nutanix.py:30 +msgid "Default" +msgstr "" + +#: assets/models/group.py:34 +msgid "Default asset group" +msgstr "" + +#: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:924 +msgid "System" +msgstr "" + +#: assets/models/label.py:18 assets/models/node.py:553 +#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 +#: authentication/models/connection_token.py:22 +#: common/drf/serializers/common.py:82 settings/models.py:34 +msgid "Value" +msgstr "" + +#: assets/models/label.py:36 assets/serializers/cagegory.py:6 +#: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 +#: settings/serializers/sms.py:7 +msgid "Label" +msgstr "" + +#: assets/models/node.py:158 +msgid "New node" +msgstr "" + +#: assets/models/node.py:481 +msgid "empty" +msgstr "" + +#: assets/models/node.py:552 perms/models/perm_node.py:21 +msgid "Key" +msgstr "" + +#: assets/models/node.py:554 assets/serializers/node.py:20 +msgid "Full value" +msgstr "" + +#: assets/models/node.py:558 perms/models/perm_node.py:22 +msgid "Parent key" +msgstr "" + +#: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 +#: xpack/plugins/cloud/serializers/task.py:74 +msgid "Node" +msgstr "" + +#: assets/models/node.py:570 +msgid "Can match node" +msgstr "" + +#: assets/models/platform.py:20 +msgid "Required" +msgstr "" + +#: assets/models/platform.py:23 settings/serializers/settings.py:61 +#: users/templates/users/reset_password.html:29 +msgid "Setting" +msgstr "" + +#: assets/models/platform.py:42 audits/const.py:69 settings/models.py:37 +#: terminal/serializers/applet_host.py:26 +msgid "Enabled" +msgstr "" + +#: assets/models/platform.py:43 +msgid "Ansible config" +msgstr "" + +#: assets/models/platform.py:44 +msgid "Ping enabled" +msgstr "" + +#: assets/models/platform.py:45 +msgid "Ping method" +msgstr "" + +#: assets/models/platform.py:46 assets/models/platform.py:56 +msgid "Gather facts enabled" +msgstr "" + +#: assets/models/platform.py:47 assets/models/platform.py:58 +msgid "Gather facts method" +msgstr "" + +#: assets/models/platform.py:48 +msgid "Push account enabled" +msgstr "" + +#: assets/models/platform.py:49 +msgid "Push account method" +msgstr "" + +#: assets/models/platform.py:50 +msgid "Change password enabled" +msgstr "" + +#: assets/models/platform.py:52 +msgid "Change password method" +msgstr "" + +#: assets/models/platform.py:53 +msgid "Verify account enabled" +msgstr "" + +#: assets/models/platform.py:55 +msgid "Verify account method" +msgstr "" + +#: assets/models/platform.py:75 tickets/models/ticket/general.py:299 +msgid "Meta" +msgstr "" + +#: assets/models/platform.py:76 +msgid "Internal" +msgstr "" + +#: assets/models/platform.py:80 assets/serializers/platform.py:97 +msgid "Charset" +msgstr "" + +#: assets/models/platform.py:82 +msgid "Domain enabled" +msgstr "" + +#: assets/models/platform.py:83 +msgid "Protocols enabled" +msgstr "" + +#: assets/models/platform.py:85 +msgid "Su enabled" +msgstr "" + +#: assets/models/platform.py:86 +msgid "SU method" +msgstr "" + +#: assets/models/platform.py:88 assets/serializers/platform.py:104 +msgid "Automation" +msgstr "" + +#: assets/models/utils.py:19 +#, python-format +msgid "%(value)s is not an even number" +msgstr "" + +#: assets/notifications.py:8 +msgid "Notification of account backup route task results" +msgstr "" + +#: assets/notifications.py:18 +msgid "" +"{} - The account backup passage task has been completed. See the attachment " +"for details" +msgstr "" + +#: assets/notifications.py:20 +msgid "" +"{} - The account backup passage task has been completed: the encryption " +"password has not been set - please go to personal information -> file " +"encryption password to set the encryption password" +msgstr "" + +#: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 +msgid "Notification of implementation result of encryption change plan" +msgstr "" + +#: assets/notifications.py:41 +#: xpack/plugins/change_auth_plan/notifications.py:18 +msgid "" +"{} - The encryption change task has been completed. See the attachment for " +"details" +msgstr "" + +#: assets/notifications.py:42 +#: xpack/plugins/change_auth_plan/notifications.py:19 +msgid "" +"{} - The encryption change task has been completed: the encryption password " +"has not been set - please go to personal information -> file encryption " +"password to set the encryption password" +msgstr "" + +#: assets/serializers/account/account.py:18 +msgid "Push now" +msgstr "" + +#: assets/serializers/account/account.py:20 +msgid "Has secret" +msgstr "" + +#: assets/serializers/account/account.py:27 +msgid "Account template not found" +msgstr "" + +#: assets/serializers/account/backup.py:29 +#: assets/serializers/automations/base.py:34 ops/mixin.py:22 ops/mixin.py:102 +#: settings/serializers/auth/ldap.py:66 +#: xpack/plugins/change_auth_plan/serializers/base.py:43 +msgid "Periodic perform" +msgstr "" + +#: assets/serializers/account/backup.py:31 +#: assets/serializers/automations/change_secret.py:41 +#: xpack/plugins/change_auth_plan/serializers/base.py:46 +msgid "Currently only mail sending is supported" +msgstr "" + +#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:102 +#: authentication/serializers/connect_token_secret.py:27 +#: authentication/serializers/connect_token_secret.py:63 +#: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 +#: xpack/plugins/cloud/serializers/task.py:43 +msgid "Protocols" +msgstr "" + +#: assets/serializers/asset/common.py:88 +msgid "Address" +msgstr "" + +#: assets/serializers/asset/common.py:156 +msgid "Platform not exist" +msgstr "" + +#: assets/serializers/asset/common.py:172 +msgid "Protocol is required: {}" +msgstr "" + +#: assets/serializers/asset/host.py:12 +msgid "Vendor" +msgstr "" + +#: assets/serializers/asset/host.py:13 +msgid "Model" +msgstr "" + +#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 +msgid "Serial number" +msgstr "" + +#: assets/serializers/asset/host.py:16 +msgid "CPU model" +msgstr "" + +#: assets/serializers/asset/host.py:17 +msgid "CPU count" +msgstr "" + +#: assets/serializers/asset/host.py:18 +msgid "CPU cores" +msgstr "" + +#: assets/serializers/asset/host.py:19 +msgid "CPU vcpus" +msgstr "" + +#: assets/serializers/asset/host.py:20 +msgid "Memory" +msgstr "" + +#: assets/serializers/asset/host.py:21 +msgid "Disk total" +msgstr "" + +#: assets/serializers/asset/host.py:22 +msgid "Disk info" +msgstr "" + +#: assets/serializers/asset/host.py:24 +msgid "OS" +msgstr "" + +#: assets/serializers/asset/host.py:25 +msgid "OS version" +msgstr "" + +#: assets/serializers/asset/host.py:26 +msgid "OS arch" +msgstr "" + +#: assets/serializers/asset/host.py:27 +msgid "Hostname raw" +msgstr "" + +#: assets/serializers/asset/host.py:28 +msgid "Asset number" +msgstr "" + +#: assets/serializers/automations/change_secret.py:28 +#: xpack/plugins/change_auth_plan/models/asset.py:50 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +msgid "SSH Key strategy" +msgstr "" + +#: assets/serializers/automations/change_secret.py:70 +#: xpack/plugins/change_auth_plan/serializers/base.py:58 +msgid "* Please enter the correct password length" +msgstr "" + +#: assets/serializers/automations/change_secret.py:73 +#: xpack/plugins/change_auth_plan/serializers/base.py:61 +msgid "* Password length range 6-30 bits" +msgstr "" + +#: assets/serializers/automations/change_secret.py:117 +#: assets/serializers/automations/change_secret.py:145 audits/const.py:74 +#: audits/models.py:40 common/const/choices.py:18 ops/serializers/celery.py:39 +#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 +#: xpack/plugins/change_auth_plan/serializers/asset.py:189 +msgid "Success" +msgstr "" + +#: assets/serializers/automations/gather_accounts.py:23 +msgid "Executed amount" +msgstr "" + +#: assets/serializers/base.py:21 +msgid "Key password" +msgstr "" + +#: assets/serializers/cagegory.py:9 +msgid "Constraints" +msgstr "" + +#: assets/serializers/cagegory.py:15 +msgid "Types" +msgstr "" + +#: assets/serializers/domain.py:16 +msgid "Gateway" +msgstr "" + +#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:9 +msgid "Hostname" +msgstr "" + +#: assets/serializers/label.py:12 +msgid "Assets amount" +msgstr "" + +#: assets/serializers/label.py:13 +msgid "Category display" +msgstr "" + +#: assets/serializers/node.py:17 +msgid "value" +msgstr "" + +#: assets/serializers/node.py:31 +msgid "Can't contains: /" +msgstr "" + +#: assets/serializers/node.py:41 +msgid "The same level node name cannot be the same" +msgstr "" + +#: assets/serializers/platform.py:24 +msgid "SFTP enabled" +msgstr "" + +#: assets/serializers/platform.py:25 +msgid "SFTP home" +msgstr "" + +#: assets/serializers/platform.py:28 +msgid "Auto fill" +msgstr "" + +#: assets/serializers/platform.py:79 +msgid "Primary" +msgstr "" + +#: assets/serializers/utils.py:13 +msgid "Password can not contains `{{` " +msgstr "" + +#: assets/serializers/utils.py:16 +msgid "Password can not contains `'` " +msgstr "" + +#: assets/serializers/utils.py:18 +msgid "Password can not contains `\"` " +msgstr "" + +#: assets/serializers/utils.py:24 +msgid "private key invalid or passphrase error" +msgstr "" + +#: assets/tasks/automation.py:11 +msgid "Execute automation" +msgstr "" + +#: assets/tasks/backup.py:13 +msgid "Execute account backup plan" +msgstr "" + +#: assets/tasks/gather_accounts.py:31 +msgid "Gather assets accounts" +msgstr "" + +#: assets/tasks/gather_facts.py:26 +msgid "Update some assets hardware info. " +msgstr "" + +#: assets/tasks/gather_facts.py:44 +msgid "Manually update the hardware information of assets" +msgstr "" + +#: assets/tasks/gather_facts.py:49 +msgid "Update assets hardware info: " +msgstr "" + +#: assets/tasks/gather_facts.py:53 +msgid "Manually update the hardware information of assets under a node" +msgstr "" + +#: assets/tasks/gather_facts.py:59 +msgid "Update node asset hardware information: " +msgstr "" + +#: assets/tasks/nodes_amount.py:16 +msgid "Check the amount of assets under the node" +msgstr "" + +#: assets/tasks/nodes_amount.py:28 +msgid "" +"The task of self-checking is already running and cannot be started repeatedly" +msgstr "" + +#: assets/tasks/nodes_amount.py:34 +msgid "Periodic check the amount of assets under the node" +msgstr "" + +#: assets/tasks/ping.py:21 assets/tasks/ping.py:39 +msgid "Test assets connectivity " +msgstr "" + +#: assets/tasks/ping.py:33 +msgid "Manually test the connectivity of a asset" +msgstr "" + +#: assets/tasks/ping.py:43 +msgid "Manually test the connectivity of assets under a node" +msgstr "" + +#: assets/tasks/ping.py:49 +msgid "Test if the assets under the node are connectable " +msgstr "" + +#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 +msgid "Push accounts to assets" +msgstr "" + +#: assets/tasks/utils.py:17 +msgid "Asset has been disabled, skipped: {}" +msgstr "" + +#: assets/tasks/utils.py:21 +msgid "Asset may not be support ansible, skipped: {}" +msgstr "" + +#: assets/tasks/utils.py:39 +msgid "For security, do not push user {}" +msgstr "" + +#: assets/tasks/utils.py:55 +msgid "No assets matched, stop task" +msgstr "" + +#: assets/tasks/verify_account.py:30 +msgid "Verify asset account availability" +msgstr "" + +#: assets/tasks/verify_account.py:37 +msgid "Verify accounts connectivity" +msgstr "" + +#: audits/apps.py:9 +msgid "Audits" +msgstr "" + +#: audits/backends/db.py:12 +msgid "The text content is too long. Use Elasticsearch to store operation logs" +msgstr "" + +#: audits/backends/db.py:24 audits/backends/db.py:26 +msgid "Tips" +msgstr "" + +#: audits/const.py:45 +msgid "Mkdir" +msgstr "" + +#: audits/const.py:46 +msgid "Rmdir" +msgstr "" + +#: audits/const.py:47 audits/const.py:57 +#: authentication/templates/authentication/_access_key_modal.html:65 +#: rbac/tree.py:226 +msgid "Delete" +msgstr "" + +#: audits/const.py:48 perms/const.py:13 +msgid "Upload" +msgstr "" + +#: audits/const.py:49 +msgid "Rename" +msgstr "" + +#: audits/const.py:50 +msgid "Symlink" +msgstr "" + +#: audits/const.py:51 perms/const.py:14 +msgid "Download" +msgstr "" + +#: audits/const.py:55 rbac/tree.py:224 +msgid "View" +msgstr "" + +#: audits/const.py:56 rbac/tree.py:225 templates/_csv_import_export.html:18 +#: templates/_csv_update_modal.html:6 +msgid "Update" +msgstr "" + +#: audits/const.py:58 +#: authentication/templates/authentication/_access_key_modal.html:22 +#: rbac/tree.py:223 +msgid "Create" +msgstr "" + +#: audits/const.py:63 settings/serializers/terminal.py:6 +#: terminal/models/applet/host.py:24 terminal/models/component/terminal.py:159 +msgid "Terminal" +msgstr "" + +#: audits/const.py:70 +msgid "-" +msgstr "" + +#: audits/handler.py:134 +msgid "Yes" +msgstr "" + +#: audits/handler.py:134 +msgid "No" +msgstr "" + +#: audits/models.py:32 audits/models.py:55 audits/models.py:96 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 +msgid "Remote addr" +msgstr "" + +#: audits/models.py:37 audits/serializers.py:19 +msgid "Operate" +msgstr "" + +#: audits/models.py:39 +msgid "Filename" +msgstr "" + +#: audits/models.py:44 +msgid "File transfer log" +msgstr "" + +#: audits/models.py:53 audits/serializers.py:91 +msgid "Resource Type" +msgstr "" + +#: audits/models.py:54 +msgid "Resource" +msgstr "" + +#: audits/models.py:56 audits/models.py:98 +#: terminal/backends/command/serializers.py:41 +msgid "Datetime" +msgstr "" + +#: audits/models.py:88 +msgid "Operate log" +msgstr "" + +#: audits/models.py:94 +msgid "Change by" +msgstr "" + +#: audits/models.py:104 +msgid "Password change log" +msgstr "" + +#: audits/models.py:111 +msgid "Login type" +msgstr "" + +#: audits/models.py:113 tickets/models/ticket/login_confirm.py:10 +msgid "Login ip" +msgstr "" + +#: audits/models.py:115 +#: authentication/templates/authentication/_msg_different_city.html:11 +#: tickets/models/ticket/login_confirm.py:11 +msgid "Login city" +msgstr "" + +#: audits/models.py:118 audits/serializers.py:62 +msgid "User agent" +msgstr "" + +#: audits/models.py:121 audits/serializers.py:39 +#: authentication/templates/authentication/_mfa_confirm_modal.html:14 +#: users/forms/profile.py:65 users/models/user.py:698 +#: users/serializers/profile.py:126 +msgid "MFA" +msgstr "" + +#: audits/models.py:131 +msgid "Date login" +msgstr "" + +#: audits/models.py:133 audits/serializers.py:64 +msgid "Authentication backend" +msgstr "" + +#: audits/models.py:174 +msgid "User login log" +msgstr "" + +#: audits/serializers.py:63 +msgid "Reason display" +msgstr "" + +#: audits/signal_handlers.py:48 +msgid "SSH Key" +msgstr "" + +#: audits/signal_handlers.py:50 settings/serializers/auth/sso.py:10 +msgid "SSO" +msgstr "" + +#: audits/signal_handlers.py:51 +msgid "Auth Token" +msgstr "" + +#: audits/signal_handlers.py:52 authentication/notifications.py:73 +#: authentication/views/login.py:73 authentication/views/wecom.py:178 +#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 +#: users/models/user.py:736 +msgid "WeCom" +msgstr "" + +#: audits/signal_handlers.py:53 authentication/views/feishu.py:145 +#: authentication/views/login.py:85 notifications/backends/__init__.py:14 +#: settings/serializers/auth/feishu.py:10 users/models/user.py:738 +msgid "FeiShu" +msgstr "" + +#: audits/signal_handlers.py:54 authentication/views/dingtalk.py:180 +#: authentication/views/login.py:79 notifications/backends/__init__.py:12 +#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:737 +msgid "DingTalk" +msgstr "" + +#: audits/signal_handlers.py:55 authentication/models/temp_token.py:16 +msgid "Temporary token" +msgstr "" + +#: authentication/api/confirm.py:40 +msgid "This action require verify your MFA" +msgstr "" + +#: authentication/api/mfa.py:59 +msgid "Current user not support mfa type: {}" +msgstr "" + +#: authentication/api/password.py:31 terminal/api/session/session.py:225 +#: users/views/profile/reset.py:44 +msgid "User does not exist: {}" +msgstr "" + +#: authentication/api/password.py:31 users/views/profile/reset.py:127 +msgid "No user matched" +msgstr "" + +#: authentication/api/password.py:35 +msgid "" +"The user is from {}, please go to the corresponding system to change the " +"password" +msgstr "" + +#: authentication/api/password.py:59 +#: authentication/templates/authentication/login.html:256 +#: users/templates/users/forgot_password.html:27 +#: users/templates/users/forgot_password.html:28 +#: users/templates/users/forgot_password_previewing.html:13 +#: users/templates/users/forgot_password_previewing.html:14 +msgid "Forgot password" +msgstr "" + +#: authentication/apps.py:7 settings/serializers/auth/base.py:10 +#: settings/serializers/auth/cas.py:10 settings/serializers/auth/dingtalk.py:10 +#: settings/serializers/auth/feishu.py:10 settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/oauth2.py:19 settings/serializers/auth/oidc.py:12 +#: settings/serializers/auth/radius.py:13 settings/serializers/auth/saml2.py:11 +#: settings/serializers/auth/sso.py:10 settings/serializers/auth/wecom.py:10 +msgid "Authentication" +msgstr "" + +#: authentication/backends/custom.py:58 +#: authentication/backends/oauth2/backends.py:158 +msgid "User invalid, disabled or expired" +msgstr "" + +#: authentication/backends/drf.py:56 +msgid "Invalid signature header. No credentials provided." +msgstr "" + +#: authentication/backends/drf.py:59 +msgid "Invalid signature header. Signature string should not contain spaces." +msgstr "" + +#: authentication/backends/drf.py:66 +msgid "Invalid signature header. Format like AccessKeyId:Signature" +msgstr "" + +#: authentication/backends/drf.py:70 +msgid "" +"Invalid signature header. Signature string should not contain invalid " +"characters." +msgstr "" + +#: authentication/backends/drf.py:90 authentication/backends/drf.py:106 +msgid "Invalid signature." +msgstr "" + +#: authentication/backends/drf.py:97 +msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" +msgstr "" + +#: authentication/backends/drf.py:102 +msgid "Expired, more than 15 minutes" +msgstr "" + +#: authentication/backends/drf.py:109 +msgid "User disabled." +msgstr "" + +#: authentication/backends/drf.py:127 +msgid "Invalid token header. No credentials provided." +msgstr "" + +#: authentication/backends/drf.py:130 +msgid "Invalid token header. Sign string should not contain spaces." +msgstr "" + +#: authentication/backends/drf.py:137 +msgid "" +"Invalid token header. Sign string should not contain invalid characters." +msgstr "" + +#: authentication/backends/drf.py:148 +msgid "Invalid token or cache refreshed." +msgstr "" + +#: authentication/confirm/password.py:16 +msgid "Authentication failed password incorrect" +msgstr "" + +#: authentication/confirm/relogin.py:10 +msgid "Login time has exceeded {} minutes, please login again" +msgstr "" + +#: authentication/errors/const.py:18 +msgid "Username/password check failed" +msgstr "" + +#: authentication/errors/const.py:19 +msgid "Password decrypt failed" +msgstr "" + +#: authentication/errors/const.py:20 +msgid "MFA failed" +msgstr "" + +#: authentication/errors/const.py:21 +msgid "MFA unset" +msgstr "" + +#: authentication/errors/const.py:22 +msgid "Username does not exist" +msgstr "" + +#: authentication/errors/const.py:23 +msgid "Password expired" +msgstr "" + +#: authentication/errors/const.py:24 +msgid "Disabled or expired" +msgstr "" + +#: authentication/errors/const.py:25 +msgid "This account is inactive." +msgstr "" + +#: authentication/errors/const.py:26 +msgid "This account is expired" +msgstr "" + +#: authentication/errors/const.py:27 +msgid "Auth backend not match" +msgstr "" + +#: authentication/errors/const.py:28 +msgid "ACL is not allowed" +msgstr "" + +#: authentication/errors/const.py:29 +msgid "Only local users are allowed" +msgstr "" + +#: authentication/errors/const.py:39 +msgid "No session found, check your cookie" +msgstr "" + +#: authentication/errors/const.py:41 +#, python-brace-format +msgid "" +"The username or password you entered is incorrect, please enter it again. " +"You can also try {times_try} times (The account will be temporarily locked " +"for {block_time} minutes)" +msgstr "" + +#: authentication/errors/const.py:47 authentication/errors/const.py:55 +msgid "" +"The account has been locked (please contact admin to unlock it or try again " +"after {} minutes)" +msgstr "" + +#: authentication/errors/const.py:51 +msgid "" +"The address has been locked (please contact admin to unlock it or try again " +"after {} minutes)" +msgstr "" + +#: authentication/errors/const.py:59 +#, python-brace-format +msgid "" +"{error}, You can also try {times_try} times (The account will be temporarily " +"locked for {block_time} minutes)" +msgstr "" + +#: authentication/errors/const.py:63 +msgid "MFA required" +msgstr "" + +#: authentication/errors/const.py:64 +msgid "MFA not set, please set it first" +msgstr "" + +#: authentication/errors/const.py:65 +msgid "Login confirm required" +msgstr "" + +#: authentication/errors/const.py:66 +msgid "Wait login confirm ticket for accept" +msgstr "" + +#: authentication/errors/const.py:67 +msgid "Login confirm ticket was {}" +msgstr "" + +#: authentication/errors/failed.py:146 +msgid "Current IP and Time period is not allowed" +msgstr "" + +#: authentication/errors/failed.py:151 +msgid "Please enter MFA code" +msgstr "" + +#: authentication/errors/failed.py:156 +msgid "Please enter SMS code" +msgstr "" + +#: authentication/errors/failed.py:161 users/exceptions.py:15 +msgid "Phone not set" +msgstr "" + +#: authentication/errors/mfa.py:8 +msgid "SSO auth closed" +msgstr "" + +#: authentication/errors/mfa.py:18 authentication/views/wecom.py:80 +msgid "WeCom is already bound" +msgstr "" + +#: authentication/errors/mfa.py:23 authentication/views/wecom.py:237 +#: authentication/views/wecom.py:291 +msgid "WeCom is not bound" +msgstr "" + +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 +#: authentication/views/dingtalk.py:297 +msgid "DingTalk is not bound" +msgstr "" + +#: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 +msgid "FeiShu is not bound" +msgstr "" + +#: authentication/errors/mfa.py:38 +msgid "Your password is invalid" +msgstr "" + +#: authentication/errors/redirect.py:85 authentication/mixins.py:306 +msgid "Your password is too simple, please change it for security" +msgstr "" + +#: authentication/errors/redirect.py:93 authentication/mixins.py:313 +msgid "You should to change your password before login" +msgstr "" + +#: authentication/errors/redirect.py:101 authentication/mixins.py:320 +msgid "Your password has expired, please reset before logging in" +msgstr "" + +#: authentication/forms.py:45 +msgid "{} days auto login" +msgstr "" + +#: authentication/forms.py:56 +msgid "MFA Code" +msgstr "" + +#: authentication/forms.py:57 +msgid "MFA type" +msgstr "" + +#: authentication/forms.py:65 +#: authentication/templates/authentication/_captcha_field.html:15 +msgid "Captcha" +msgstr "" + +#: authentication/forms.py:70 users/forms/profile.py:28 +msgid "MFA code" +msgstr "" + +#: authentication/forms.py:72 +msgid "Dynamic code" +msgstr "" + +#: authentication/mfa/base.py:7 +msgid "Please input security code" +msgstr "" + +#: authentication/mfa/custom.py:20 +msgid "MFA Custom code invalid" +msgstr "" + +#: authentication/mfa/custom.py:26 +msgid "MFA custom verification code" +msgstr "" + +#: authentication/mfa/custom.py:56 +msgid "MFA custom global enabled, cannot disable" +msgstr "" + +#: authentication/mfa/otp.py:7 +msgid "OTP code invalid, or server time error" +msgstr "" + +#: authentication/mfa/otp.py:12 +msgid "OTP" +msgstr "" + +#: authentication/mfa/otp.py:13 +msgid "OTP verification code" +msgstr "" + +#: authentication/mfa/otp.py:48 +msgid "Virtual OTP based MFA" +msgstr "" + +#: authentication/mfa/radius.py:7 +msgid "Radius verify code invalid" +msgstr "" + +#: authentication/mfa/radius.py:13 +msgid "Radius verification code" +msgstr "" + +#: authentication/mfa/radius.py:44 +msgid "Radius global enabled, cannot disable" +msgstr "" + +#: authentication/mfa/sms.py:7 +msgid "SMS verify code invalid" +msgstr "" + +#: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:16 +#: authentication/serializers/password_mfa.py:24 +#: settings/serializers/auth/sms.py:27 users/forms/profile.py:103 +#: users/forms/profile.py:106 users/templates/users/forgot_password.html:111 +#: users/views/profile/reset.py:79 +msgid "SMS" +msgstr "" + +#: authentication/mfa/sms.py:13 +msgid "SMS verification code" +msgstr "" + +#: authentication/mfa/sms.py:57 +msgid "Set phone number to enable" +msgstr "" + +#: authentication/mfa/sms.py:61 +msgid "Clear phone number to disable" +msgstr "" + +#: authentication/middleware.py:77 settings/utils/ldap.py:652 +msgid "Authentication failed (before login check failed): {}" +msgstr "" + +#: authentication/mixins.py:256 +msgid "The MFA type ({}) is not enabled" +msgstr "" + +#: authentication/mixins.py:296 +msgid "Please change your password" +msgstr "" + +#: authentication/models/connection_token.py:31 +#: terminal/serializers/storage.py:111 +msgid "Account name" +msgstr "" + +#: authentication/models/connection_token.py:32 +msgid "Input username" +msgstr "" + +#: authentication/models/connection_token.py:33 +msgid "Input secret" +msgstr "" + +#: authentication/models/connection_token.py:35 +#: authentication/serializers/connect_token_secret.py:110 +#: perms/models/perm_token.py:17 +msgid "Connect method" +msgstr "" + +#: authentication/models/connection_token.py:36 +#: rbac/serializers/rolebinding.py:21 +msgid "User display" +msgstr "" + +#: authentication/models/connection_token.py:37 +msgid "Asset display" +msgstr "" + +#: authentication/models/connection_token.py:38 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 +#: tickets/models/ticket/apply_application.py:31 +#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:719 +msgid "Date expired" +msgstr "" + +#: authentication/models/connection_token.py:42 +msgid "Connection token" +msgstr "" + +#: authentication/models/connection_token.py:44 +msgid "Can view connection token secret" +msgstr "" + +#: authentication/models/connection_token.py:91 +msgid "Connection token expired at: {}" +msgstr "" + +#: authentication/models/connection_token.py:94 +msgid "No user or invalid user" +msgstr "" + +#: authentication/models/connection_token.py:98 +msgid "No asset or inactive asset" +msgstr "" + +#: authentication/models/connection_token.py:173 +msgid "Super connection token" +msgstr "" + +#: authentication/models/private_token.py:9 +msgid "Private Token" +msgstr "" + +#: authentication/models/sso_token.py:15 +msgid "Expired" +msgstr "" + +#: authentication/models/sso_token.py:20 +msgid "SSO token" +msgstr "" + +#: authentication/models/temp_token.py:11 +msgid "Verified" +msgstr "" + +#: authentication/notifications.py:19 +msgid "Different city login reminder" +msgstr "" + +#: authentication/notifications.py:52 +msgid "binding reminder" +msgstr "" + +#: authentication/serializers/connect_token_secret.py:109 +msgid "Expired now" +msgstr "" + +#: authentication/serializers/connection_token.py:14 +msgid "Expired time" +msgstr "" + +#: authentication/serializers/password_mfa.py:16 +#: authentication/serializers/password_mfa.py:24 +#: notifications/backends/__init__.py:10 settings/serializers/email.py:19 +#: settings/serializers/email.py:50 users/forms/profile.py:102 +#: users/forms/profile.py:106 users/models/user.py:677 +#: users/templates/users/forgot_password.html:116 +#: users/views/profile/reset.py:73 +msgid "Email" +msgstr "" + +#: authentication/serializers/password_mfa.py:29 +#: users/templates/users/forgot_password.html:107 +msgid "The {} cannot be empty" +msgstr "" + +#: authentication/serializers/token.py:79 perms/serializers/permission.py:30 +#: perms/serializers/permission.py:61 users/serializers/user.py:203 +msgid "Is valid" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:6 +msgid "API key list" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:18 +msgid "Using api key sign api header, every requests header difference" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:19 +msgid "docs" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:30 +#: users/serializers/group.py:35 +msgid "ID" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:33 +#: terminal/notifications.py:96 terminal/notifications.py:144 +msgid "Date" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:48 +msgid "Show" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:66 +#: settings/serializers/security.py:39 users/models/user.py:559 +#: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 +#: users/templates/users/user_verify_mfa.html:36 +msgid "Disable" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:67 +#: users/models/user.py:560 users/serializers/profile.py:117 +#: users/templates/users/mfa_setting.html:26 +#: users/templates/users/mfa_setting.html:68 +msgid "Enable" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:147 +msgid "Delete success" +msgstr "" + +#: authentication/templates/authentication/_access_key_modal.html:155 +#: authentication/templates/authentication/_mfa_confirm_modal.html:53 +#: templates/_modal.html:22 tickets/const.py:44 +msgid "Close" +msgstr "" + +#: authentication/templates/authentication/_captcha_field.html:8 +msgid "Play CAPTCHA as audio file" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:5 +msgid "MFA confirm" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:17 +msgid "Need MFA for view auth" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:20 +#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:37 +#: templates/_modal.html:23 templates/flash_message_standalone.html:37 +#: users/templates/users/user_password_verify.html:20 +msgid "Confirm" +msgstr "" + +#: authentication/templates/authentication/_mfa_confirm_modal.html:25 +msgid "Code error" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:3 +#: authentication/templates/authentication/_msg_oauth_bind.html:3 +#: authentication/templates/authentication/_msg_reset_password.html:3 +#: authentication/templates/authentication/_msg_reset_password_code.html:9 +#: authentication/templates/authentication/_msg_rest_password_success.html:2 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 +#: jumpserver/conf.py:414 +#: perms/templates/perms/_msg_item_permissions_expire.html:3 +#: perms/templates/perms/_msg_permed_items_expire.html:3 +#: tickets/templates/tickets/approve_check_password.html:33 +#: users/templates/users/_msg_account_expire_reminder.html:4 +#: users/templates/users/_msg_password_expire_reminder.html:4 +#: users/templates/users/_msg_reset_mfa.html:4 +#: users/templates/users/_msg_reset_ssh_key.html:4 +msgid "Hello" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:6 +msgid "Your account has remote login behavior, please pay attention" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:10 +msgid "Login time" +msgstr "" + +#: authentication/templates/authentication/_msg_different_city.html:16 +msgid "" +"If you suspect that the login behavior is abnormal, please modify the " +"account password in time." +msgstr "" + +#: authentication/templates/authentication/_msg_oauth_bind.html:6 +msgid "Your account has just been bound to" +msgstr "" + +#: authentication/templates/authentication/_msg_oauth_bind.html:17 +msgid "If the operation is not your own, unbind and change the password." +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:6 +msgid "" +"Please click the link below to reset your password, if not your request, " +"concern your account security" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:10 +msgid "Click here reset password" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:16 +#: users/templates/users/_msg_user_created.html:22 +msgid "This link is valid for 1 hour. After it expires" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password.html:17 +#: users/templates/users/_msg_user_created.html:23 +msgid "request new one" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password_code.html:12 +#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 +#: users/forms/profile.py:104 users/templates/users/forgot_password.html:65 +msgid "Verify code" +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password_code.html:15 +msgid "" +"Copy the verification code to the Reset Password page to reset the password." +msgstr "" + +#: authentication/templates/authentication/_msg_reset_password_code.html:18 +msgid "The validity period of the verification code is one minute" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:5 +msgid "Your password has just been successfully updated" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:9 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:9 +msgid "Browser" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:13 +msgid "" +"If the password update was not initiated by you, your account may have " +"security issues" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_password_success.html:14 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:14 +msgid "If you have any questions, you can contact the administrator" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_public_key_success.html:5 +msgid "Your public key has just been successfully updated" +msgstr "" + +#: authentication/templates/authentication/_msg_rest_public_key_success.html:13 +msgid "" +"If the public key update was not initiated by you, your account may have " +"security issues" +msgstr "" + +#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 +#: templates/flash_message_standalone.html:28 tickets/const.py:17 +msgid "Cancel" +msgstr "" + +#: authentication/templates/authentication/login.html:221 +msgid "Welcome back, please enter username and password to login" +msgstr "" + +#: authentication/templates/authentication/login.html:264 +#: templates/_header_bar.html:89 +msgid "Login" +msgstr "" + +#: authentication/templates/authentication/login.html:271 +msgid "More login options" +msgstr "" + +#: authentication/templates/authentication/login_mfa.html:6 +msgid "MFA Auth" +msgstr "" + +#: authentication/templates/authentication/login_mfa.html:19 +#: users/templates/users/user_otp_check_password.html:12 +#: users/templates/users/user_otp_enable_bind.html:24 +#: users/templates/users/user_otp_enable_install_app.html:29 +#: users/templates/users/user_verify_mfa.html:30 +msgid "Next" +msgstr "" + +#: authentication/templates/authentication/login_mfa.html:22 +msgid "Can't provide security? Please contact the administrator!" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:41 +msgid "Refresh" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:46 +msgid "Copy link" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:51 +msgid "Return" +msgstr "" + +#: authentication/templates/authentication/login_wait_confirm.html:116 +msgid "Copy success" +msgstr "" + +#: authentication/utils.py:28 common/utils/ip/geoip/utils.py:24 +#: xpack/plugins/cloud/const.py:27 +msgid "LAN" +msgstr "" + +#: authentication/views/dingtalk.py:42 +msgid "DingTalk Error, Please contact your system administrator" +msgstr "" + +#: authentication/views/dingtalk.py:45 +msgid "DingTalk Error" +msgstr "" + +#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 +#: authentication/views/wecom.py:56 +msgid "" +"The system configuration is incorrect. Please contact your administrator" +msgstr "" + +#: authentication/views/dingtalk.py:81 +msgid "DingTalk is already bound" +msgstr "" + +#: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 +msgid "Invalid user_id" +msgstr "" + +#: authentication/views/dingtalk.py:165 +msgid "DingTalk query user failed" +msgstr "" + +#: authentication/views/dingtalk.py:174 +msgid "The DingTalk is already bound to another user" +msgstr "" + +#: authentication/views/dingtalk.py:181 +msgid "Binding DingTalk successfully" +msgstr "" + +#: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 +msgid "Failed to get user from DingTalk" +msgstr "" + +#: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 +msgid "Please login with a password and then bind the DingTalk" +msgstr "" + +#: authentication/views/feishu.py:40 +msgid "FeiShu Error" +msgstr "" + +#: authentication/views/feishu.py:88 +msgid "FeiShu is already bound" +msgstr "" + +#: authentication/views/feishu.py:130 +msgid "FeiShu query user failed" +msgstr "" + +#: authentication/views/feishu.py:139 +msgid "The FeiShu is already bound to another user" +msgstr "" + +#: authentication/views/feishu.py:146 +msgid "Binding FeiShu successfully" +msgstr "" + +#: authentication/views/feishu.py:198 +msgid "Failed to get user from FeiShu" +msgstr "" + +#: authentication/views/feishu.py:205 +msgid "Please login with a password and then bind the FeiShu" +msgstr "" + +#: authentication/views/login.py:181 +msgid "Redirecting" +msgstr "" + +#: authentication/views/login.py:182 +msgid "Redirecting to {} authentication" +msgstr "" + +#: authentication/views/login.py:205 +msgid "Please enable cookies and try again." +msgstr "" + +#: authentication/views/login.py:307 +msgid "" +"Wait for {} confirm, You also can copy link to her/him
\n" +" Don't close this page" +msgstr "" + +#: authentication/views/login.py:312 +msgid "No ticket found" +msgstr "" + +#: authentication/views/login.py:348 +msgid "Logout success" +msgstr "" + +#: authentication/views/login.py:349 +msgid "Logout success, return login page" +msgstr "" + +#: authentication/views/wecom.py:41 +msgid "WeCom Error, Please contact your system administrator" +msgstr "" + +#: authentication/views/wecom.py:44 +msgid "WeCom Error" +msgstr "" + +#: authentication/views/wecom.py:163 +msgid "WeCom query user failed" +msgstr "" + +#: authentication/views/wecom.py:172 +msgid "The WeCom is already bound to another user" +msgstr "" + +#: authentication/views/wecom.py:179 +msgid "Binding WeCom successfully" +msgstr "" + +#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 +msgid "Failed to get user from WeCom" +msgstr "" + +#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 +msgid "Please login with a password and then bind the WeCom" +msgstr "" + +#: common/const/__init__.py:6 +#, python-format +msgid "%(name)s was created successfully" +msgstr "" + +#: common/const/__init__.py:7 +#, python-format +msgid "%(name)s was updated successfully" +msgstr "" + +#: common/const/choices.py:10 +msgid "Manual trigger" +msgstr "" + +#: common/const/choices.py:11 +msgid "Timing trigger" +msgstr "" + +#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 +msgid "Ready" +msgstr "" + +#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 +msgid "Pending" +msgstr "" + +#: common/const/choices.py:17 +msgid "Running" +msgstr "" + +#: common/const/choices.py:21 +msgid "Canceled" +msgstr "" + +#: common/db/encoder.py:11 +msgid "ugettext_lazy" +msgstr "" + +#: common/db/fields.py:94 +msgid "Marshal dict data to char field" +msgstr "" + +#: common/db/fields.py:98 +msgid "Marshal dict data to text field" +msgstr "" + +#: common/db/fields.py:110 +msgid "Marshal list data to char field" +msgstr "" + +#: common/db/fields.py:114 +msgid "Marshal list data to text field" +msgstr "" + +#: common/db/fields.py:118 +msgid "Marshal data to char field" +msgstr "" + +#: common/db/fields.py:122 +msgid "Marshal data to text field" +msgstr "" + +#: common/db/fields.py:164 +msgid "Encrypt field using Secret Key" +msgstr "" + +#: common/db/models.py:76 +msgid "Updated by" +msgstr "" + +#: common/drf/exc_handlers.py:25 +msgid "Object" +msgstr "" + +#: common/drf/fields.py:77 tickets/serializers/ticket/common.py:58 +#: xpack/plugins/change_auth_plan/serializers/asset.py:64 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:101 +#: xpack/plugins/cloud/serializers/account_attrs.py:56 +msgid "This field is required." +msgstr "" + +#: common/drf/fields.py:78 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "" + +#: common/drf/fields.py:79 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "" + +#: common/drf/fields.py:141 +msgid "Invalid data type, should be list" +msgstr "" + +#: common/drf/fields.py:156 +msgid "Invalid choice: {}" +msgstr "" + +#: common/drf/parsers/base.py:17 +msgid "The file content overflowed (The maximum length `{}` bytes)" +msgstr "" + +#: common/drf/parsers/base.py:159 +msgid "Parse file error: {}" +msgstr "" + +#: common/drf/serializers/common.py:86 +msgid "Children" +msgstr "" + +#: common/drf/serializers/common.py:94 +msgid "File" +msgstr "" + +#: common/exceptions.py:15 +#, python-format +msgid "%s object does not exist." +msgstr "" + +#: common/exceptions.py:25 +msgid "Someone else is doing this. Please wait for complete" +msgstr "" + +#: common/exceptions.py:30 +msgid "Your request timeout" +msgstr "" + +#: common/exceptions.py:35 +msgid "M2M reverse not allowed" +msgstr "" + +#: common/exceptions.py:41 +msgid "Is referenced by other objects and cannot be deleted" +msgstr "" + +#: common/exceptions.py:48 +msgid "This action require confirm current user" +msgstr "" + +#: common/exceptions.py:56 +msgid "Unexpect error occur" +msgstr "" + +#: common/mixins/api/action.py:52 +msgid "Request file format may be wrong" +msgstr "" + +#: common/mixins/models.py:33 +msgid "is discard" +msgstr "" + +#: common/mixins/models.py:34 +msgid "discard time" +msgstr "" + +#: common/mixins/views.py:58 +msgid "Export all" +msgstr "" + +#: common/mixins/views.py:60 +msgid "Export only selected items" +msgstr "" + +#: common/mixins/views.py:65 +#, python-format +msgid "Export filtered: %s" +msgstr "" + +#: common/plugins/es.py:28 +msgid "Invalid elasticsearch config" +msgstr "" + +#: common/plugins/es.py:33 +msgid "Not Support Elasticsearch8" +msgstr "" + +#: common/sdk/im/exceptions.py:23 +msgid "Network error, please contact system administrator" +msgstr "" + +#: common/sdk/im/wecom/__init__.py:15 +msgid "WeCom error, please contact system administrator" +msgstr "" + +#: common/sdk/sms/alibaba.py:56 +msgid "Signature does not match" +msgstr "" + +#: common/sdk/sms/cmpp2.py:46 +msgid "sp_id is 6 bits" +msgstr "" + +#: common/sdk/sms/cmpp2.py:216 +msgid "Failed to connect to the CMPP gateway server, err: {}" +msgstr "" + +#: common/sdk/sms/endpoint.py:16 +msgid "Alibaba cloud" +msgstr "" + +#: common/sdk/sms/endpoint.py:17 +msgid "Tencent cloud" +msgstr "" + +#: common/sdk/sms/endpoint.py:18 xpack/plugins/cloud/const.py:13 +msgid "Huawei Cloud" +msgstr "" + +#: common/sdk/sms/endpoint.py:19 +msgid "CMPP v2.0" +msgstr "" + +#: common/sdk/sms/endpoint.py:30 +msgid "SMS provider not support: {}" +msgstr "" + +#: common/sdk/sms/endpoint.py:51 +msgid "SMS verification code signature or template invalid" +msgstr "" + +#: common/sdk/sms/exceptions.py:8 +msgid "The verification code has expired. Please resend it" +msgstr "" + +#: common/sdk/sms/exceptions.py:13 +msgid "The verification code is incorrect" +msgstr "" + +#: common/sdk/sms/exceptions.py:18 +msgid "Please wait {} seconds before sending" +msgstr "" + +#: common/tasks.py:13 +msgid "Send email" +msgstr "" + +#: common/tasks.py:40 +msgid "Send email attachment" +msgstr "" + +#: common/utils/ip/geoip/utils.py:26 +msgid "Invalid ip" +msgstr "" + +#: common/utils/ip/utils.py:78 +msgid "Invalid address" +msgstr "" + +#: common/validators.py:14 +msgid "Special char not allowed" +msgstr "" + +#: common/validators.py:32 +msgid "This field must be unique." +msgstr "" + +#: common/validators.py:40 +msgid "Should not contains special characters" +msgstr "" + +#: common/validators.py:46 +msgid "The mobile phone number format is incorrect" +msgstr "" + +#: jumpserver/conf.py:413 +msgid "Create account successfully" +msgstr "" + +#: jumpserver/conf.py:415 +msgid "Your account has been created successfully" +msgstr "" + +#: jumpserver/context_processor.py:12 +msgid "JumpServer Open Source Bastion Host" +msgstr "" + +#: jumpserver/views/celery_flower.py:23 +msgid "

Flower service unavailable, check it

" +msgstr "" + +#: jumpserver/views/other.py:26 +msgid "" +"
Luna is a separately deployed program, you need to deploy Luna, koko, " +"configure nginx for url distribution,
If you see this page, " +"prove that you are not accessing the nginx listening port. Good luck." +msgstr "" + +#: jumpserver/views/other.py:70 +msgid "Websocket server run on port: {}, you should proxy it on nginx" +msgstr "" + +#: jumpserver/views/other.py:84 +msgid "" +"
Koko is a separately deployed program, you need to deploy Koko, " +"configure nginx for url distribution,
If you see this page, " +"prove that you are not accessing the nginx listening port. Good luck." +msgstr "" + +#: notifications/apps.py:7 +msgid "Notifications" +msgstr "" + +#: notifications/backends/__init__.py:13 +msgid "Site message" +msgstr "" + +#: notifications/models/notification.py:14 +msgid "receive backend" +msgstr "" + +#: notifications/models/notification.py:17 +msgid "User message" +msgstr "" + +#: notifications/models/notification.py:20 +msgid "{} subscription" +msgstr "" + +#: notifications/models/notification.py:32 +msgid "System message" +msgstr "" + +#: notifications/notifications.py:46 +msgid "Publish the station message" +msgstr "" + +#: ops/ansible/inventory.py:75 +msgid "No account available" +msgstr "" + +#: ops/ansible/inventory.py:178 +msgid "Ansible disabled" +msgstr "" + +#: ops/ansible/inventory.py:194 +msgid "Skip hosts below:" +msgstr "" + +#: ops/api/celery.py:63 ops/api/celery.py:78 +msgid "Waiting task start" +msgstr "" + +#: ops/apps.py:9 ops/notifications.py:16 +msgid "App ops" +msgstr "" + +#: ops/const.py:6 +msgid "Push" +msgstr "" + +#: ops/const.py:7 +msgid "Verify" +msgstr "" + +#: ops/const.py:8 +msgid "Collect" +msgstr "" + +#: ops/const.py:9 +msgid "Change password" +msgstr "" + +#: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 +msgid "Custom password" +msgstr "" + +#: ops/exception.py:6 +msgid "no valid program entry found." +msgstr "" + +#: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:73 +msgid "Cycle perform" +msgstr "" + +#: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 +#: settings/serializers/auth/ldap.py:70 +msgid "Regularly perform" +msgstr "" + +#: ops/mixin.py:108 +msgid "Interval" +msgstr "" + +#: ops/mixin.py:118 +msgid "* Please enter a valid crontab expression" +msgstr "" + +#: ops/mixin.py:125 +msgid "Range {} to {}" +msgstr "" + +#: ops/mixin.py:136 +msgid "Require periodic or regularly perform setting" +msgstr "" + +#: ops/models/adhoc.py:18 ops/models/job.py:31 +msgid "Powershell" +msgstr "" + +#: ops/models/adhoc.py:22 +msgid "Pattern" +msgstr "" + +#: ops/models/adhoc.py:24 ops/models/job.py:38 +msgid "Module" +msgstr "" + +#: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 +#: terminal/models/component/task.py:17 +msgid "Args" +msgstr "" + +#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 +#: terminal/models/session/sharing.py:24 +msgid "Creator" +msgstr "" + +#: ops/models/base.py:19 +msgid "Account policy" +msgstr "" + +#: ops/models/base.py:20 +msgid "Last execution" +msgstr "" + +#: ops/models/base.py:22 +msgid "Date last run" +msgstr "" + +#: ops/models/base.py:51 ops/models/job.py:105 +#: xpack/plugins/cloud/models.py:172 +msgid "Result" +msgstr "" + +#: ops/models/base.py:52 ops/models/job.py:106 +msgid "Summary" +msgstr "" + +#: ops/models/celery.py:55 terminal/models/component/task.py:18 +msgid "Kwargs" +msgstr "" + +#: ops/models/celery.py:56 tickets/models/comment.py:13 +#: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 +#: tickets/serializers/ticket/ticket.py:21 +msgid "State" +msgstr "" + +#: ops/models/celery.py:57 terminal/models/session/sharing.py:111 +#: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 +msgid "Finished" +msgstr "" + +#: ops/models/celery.py:58 +msgid "Date published" +msgstr "" + +#: ops/models/job.py:21 +msgid "Adhoc" +msgstr "" + +#: ops/models/job.py:22 ops/models/job.py:41 +msgid "Playbook" +msgstr "" + +#: ops/models/job.py:25 +msgid "Privileged Only" +msgstr "" + +#: ops/models/job.py:26 +msgid "Privileged First" +msgstr "" + +#: ops/models/job.py:27 +msgid "Skip" +msgstr "" + +#: ops/models/job.py:39 +msgid "Chdir" +msgstr "" + +#: ops/models/job.py:40 +msgid "Timeout (Seconds)" +msgstr "" + +#: ops/models/job.py:45 +msgid "Runas" +msgstr "" + +#: ops/models/job.py:47 +msgid "Runas policy" +msgstr "" + +#: ops/models/job.py:48 +msgid "Use Parameter Define" +msgstr "" + +#: ops/models/job.py:49 +msgid "Parameters define" +msgstr "" + +#: ops/models/job.py:104 +msgid "Parameters" +msgstr "" + +#: ops/notifications.py:17 +msgid "Server performance" +msgstr "" + +#: ops/notifications.py:23 +msgid "Terminal health check warning" +msgstr "" + +#: ops/notifications.py:68 +#, python-brace-format +msgid "The terminal is offline: {name}" +msgstr "" + +#: ops/notifications.py:73 +#, python-brace-format +msgid "Disk used more than {max_threshold}%: => {value}" +msgstr "" + +#: ops/notifications.py:78 +#, python-brace-format +msgid "Memory used more than {max_threshold}%: => {value}" +msgstr "" + +#: ops/notifications.py:83 +#, python-brace-format +msgid "CPU load more than {max_threshold}: => {value}" +msgstr "" + +#: ops/serializers/job.py:10 +msgid "Run after save" +msgstr "" + +#: ops/serializers/job.py:11 +msgid "Job type" +msgstr "任务类型" + +#: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 +#: terminal/models/component/task.py:26 +#: xpack/plugins/gathered_user/models.py:68 +msgid "Task" +msgstr "" + +#: ops/tasks.py:28 +msgid "Run ansible task" +msgstr "" + +#: ops/tasks.py:35 +msgid "Run ansible task execution" +msgstr "" + +#: ops/tasks.py:48 +msgid "Periodic clear celery tasks" +msgstr "" + +#: ops/tasks.py:50 +msgid "Clean celery log period" +msgstr "" + +#: ops/tasks.py:67 +msgid "Clear celery periodic tasks" +msgstr "" + +#: ops/tasks.py:90 +msgid "Create or update periodic tasks" +msgstr "" + +#: ops/tasks.py:98 +msgid "Periodic check service performance" +msgstr "" + +#: ops/templates/ops/celery_task_log.html:4 +msgid "Task log" +msgstr "" + +#: ops/utils.py:64 +msgid "Update task content: {}" +msgstr "" + +#: orgs/api.py:67 +msgid "The current organization ({}) cannot be deleted" +msgstr "" + +#: orgs/api.py:72 +msgid "" +"LDAP synchronization is set to the current organization. Please switch to " +"another organization before deleting" +msgstr "" + +#: orgs/api.py:81 +msgid "The organization have resource ({}) cannot be deleted" +msgstr "" + +#: orgs/apps.py:7 rbac/tree.py:113 +msgid "App organizations" +msgstr "" + +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 +#: rbac/const.py:7 rbac/models/rolebinding.py:48 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 +#: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:62 +msgid "Organization" +msgstr "" + +#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 +msgid "Org name" +msgstr "" + +#: orgs/models.py:72 +msgid "Builtin" +msgstr "" + +#: orgs/models.py:80 +msgid "GLOBAL" +msgstr "" + +#: orgs/models.py:82 +msgid "DEFAULT" +msgstr "" + +#: orgs/models.py:84 +msgid "SYSTEM" +msgstr "" + +#: orgs/models.py:90 +msgid "Can view root org" +msgstr "" + +#: orgs/models.py:91 +msgid "Can view all joined org" +msgstr "" + +#: orgs/tasks.py:9 +msgid "Refresh organization cache" +msgstr "" + +#: perms/apps.py:9 +msgid "App permissions" +msgstr "" + +#: perms/const.py:12 +msgid "Connect" +msgstr "" + +#: perms/const.py:15 +msgid "Copy" +msgstr "" + +#: perms/const.py:16 +msgid "Paste" +msgstr "" + +#: perms/const.py:26 +msgid "Transfer" +msgstr "" + +#: perms/const.py:27 +msgid "Clipboard" +msgstr "" + +#: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 +#: perms/serializers/permission.py:29 perms/serializers/permission.py:59 +#: tickets/models/ticket/apply_application.py:28 +#: tickets/models/ticket/apply_asset.py:18 +msgid "Actions" +msgstr "" + +#: perms/models/asset_permission.py:73 +msgid "From ticket" +msgstr "" + +#: perms/models/asset_permission.py:81 +msgid "Asset permission" +msgstr "" + +#: perms/models/perm_node.py:55 +msgid "Ungrouped" +msgstr "" + +#: perms/models/perm_node.py:57 +msgid "Favorite" +msgstr "" + +#: perms/models/perm_node.py:104 +msgid "Permed asset" +msgstr "" + +#: perms/models/perm_node.py:106 +msgid "Can view my assets" +msgstr "" + +#: perms/models/perm_node.py:107 +msgid "Can view user assets" +msgstr "" + +#: perms/models/perm_node.py:108 +msgid "Can view usergroup assets" +msgstr "" + +#: perms/models/perm_node.py:119 +msgid "Permed account" +msgstr "" + +#: perms/notifications.py:12 perms/notifications.py:44 +msgid "today" +msgstr "" + +#: perms/notifications.py:15 +msgid "You permed assets is about to expire" +msgstr "" + +#: perms/notifications.py:20 +msgid "permed assets" +msgstr "" + +#: perms/notifications.py:59 +msgid "Asset permissions is about to expire" +msgstr "" + +#: perms/notifications.py:64 +msgid "asset permissions of organization {}" +msgstr "" + +#: perms/serializers/permission.py:31 perms/serializers/permission.py:60 +#: users/serializers/user.py:100 users/serializers/user.py:205 +msgid "Is expired" +msgstr "" + +#: perms/templates/perms/_msg_item_permissions_expire.html:7 +#: perms/templates/perms/_msg_permed_items_expire.html:7 +#, python-format +msgid "" +"\n" +" The following %(item_type)s will expire in %(count)s days\n" +" " +msgstr "" + +#: perms/templates/perms/_msg_permed_items_expire.html:21 +msgid "If you have any question, please contact the administrator" +msgstr "" + +#: perms/utils/user_permission.py:627 rbac/tree.py:57 +msgid "My assets" +msgstr "" + +#: rbac/api/role.py:34 +msgid "Internal role, can't be destroy" +msgstr "" + +#: rbac/api/role.py:38 +msgid "The role has been bound to users, can't be destroy" +msgstr "" + +#: rbac/api/role.py:60 +msgid "Internal role, can't be update" +msgstr "" + +#: rbac/api/rolebinding.py:52 +msgid "{} at least one system role" +msgstr "" + +#: rbac/apps.py:7 +msgid "RBAC" +msgstr "" + +#: rbac/builtin.py:111 +msgid "SystemAdmin" +msgstr "" + +#: rbac/builtin.py:114 +msgid "SystemAuditor" +msgstr "" + +#: rbac/builtin.py:117 +msgid "SystemComponent" +msgstr "" + +#: rbac/builtin.py:123 +msgid "OrgAdmin" +msgstr "" + +#: rbac/builtin.py:126 +msgid "OrgAuditor" +msgstr "" + +#: rbac/builtin.py:129 +msgid "OrgUser" +msgstr "" + +#: rbac/models/menu.py:13 +msgid "Menu permission" +msgstr "" + +#: rbac/models/menu.py:15 +msgid "Can view console view" +msgstr "" + +#: rbac/models/menu.py:16 +msgid "Can view audit view" +msgstr "" + +#: rbac/models/menu.py:17 +msgid "Can view workbench view" +msgstr "" + +#: rbac/models/menu.py:18 +msgid "Can view web terminal" +msgstr "" + +#: rbac/models/menu.py:19 +msgid "Can view file manager" +msgstr "" + +#: rbac/models/permission.py:26 rbac/models/role.py:34 +msgid "Permissions" +msgstr "" + +#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 +#: settings/serializers/auth/oauth2.py:37 +msgid "Scope" +msgstr "" + +#: rbac/models/role.py:36 +msgid "Built-in" +msgstr "" + +#: rbac/models/role.py:46 rbac/models/rolebinding.py:44 +#: users/models/user.py:685 +msgid "Role" +msgstr "" + +#: rbac/models/role.py:144 +msgid "System role" +msgstr "" + +#: rbac/models/role.py:152 +msgid "Organization role" +msgstr "" + +#: rbac/models/rolebinding.py:53 +msgid "Role binding" +msgstr "" + +#: rbac/models/rolebinding.py:137 +msgid "All organizations" +msgstr "" + +#: rbac/models/rolebinding.py:166 +msgid "" +"User last role in org, can not be delete, you can remove user from org " +"instead" +msgstr "" + +#: rbac/models/rolebinding.py:173 +msgid "Organization role binding" +msgstr "" + +#: rbac/models/rolebinding.py:188 +msgid "System role binding" +msgstr "" + +#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 +msgid "Perms" +msgstr "" + +#: rbac/serializers/role.py:11 +msgid "Scope display" +msgstr "" + +#: rbac/serializers/role.py:26 users/serializers/group.py:34 +msgid "Users amount" +msgstr "" + +#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 +msgid "Display name" +msgstr "" + +#: rbac/serializers/rolebinding.py:22 +msgid "Role display" +msgstr "" + +#: rbac/serializers/rolebinding.py:56 +msgid "Has bound this role" +msgstr "" + +#: rbac/tree.py:18 rbac/tree.py:19 +msgid "All permissions" +msgstr "" + +#: rbac/tree.py:25 +msgid "Console view" +msgstr "" + +#: rbac/tree.py:26 +msgid "Workbench view" +msgstr "" + +#: rbac/tree.py:27 +msgid "Audit view" +msgstr "" + +#: rbac/tree.py:28 settings/models.py:156 +msgid "System setting" +msgstr "" + +#: rbac/tree.py:29 +msgid "Other" +msgstr "" + +#: rbac/tree.py:41 +msgid "Session audits" +msgstr "" + +#: rbac/tree.py:51 +msgid "Cloud import" +msgstr "" + +#: rbac/tree.py:52 +msgid "Backup account" +msgstr "" + +#: rbac/tree.py:53 +msgid "Gather account" +msgstr "" + +#: rbac/tree.py:54 +msgid "App change auth" +msgstr "" + +#: rbac/tree.py:55 +msgid "Asset change auth" +msgstr "" + +#: rbac/tree.py:56 +msgid "Terminal setting" +msgstr "" + +#: rbac/tree.py:58 +msgid "My apps" +msgstr "" + +#: rbac/tree.py:114 +msgid "Ticket comment" +msgstr "" + +#: rbac/tree.py:115 tickets/models/ticket/general.py:306 +msgid "Ticket" +msgstr "" + +#: rbac/tree.py:116 +msgid "Common setting" +msgstr "" + +#: rbac/tree.py:117 +msgid "View permission tree" +msgstr "" + +#: rbac/tree.py:118 +msgid "Execute batch command" +msgstr "" + +#: settings/api/dingtalk.py:31 settings/api/feishu.py:36 +#: settings/api/sms.py:148 settings/api/wecom.py:37 +msgid "Test success" +msgstr "" + +#: settings/api/email.py:20 +msgid "Test mail sent to {}, please check" +msgstr "" + +#: settings/api/ldap.py:166 +msgid "Synchronization start, please wait." +msgstr "" + +#: settings/api/ldap.py:170 +msgid "Synchronization is running, please wait." +msgstr "" + +#: settings/api/ldap.py:175 +msgid "Synchronization error: {}" +msgstr "" + +#: settings/api/ldap.py:213 +msgid "Get ldap users is None" +msgstr "" + +#: settings/api/ldap.py:222 +msgid "Imported {} users successfully (Organization: {})" +msgstr "" + +#: settings/api/sms.py:130 +msgid "Invalid SMS platform" +msgstr "" + +#: settings/api/sms.py:136 +msgid "test_phone is required" +msgstr "" + +#: settings/apps.py:7 +msgid "Settings" +msgstr "" + +#: settings/models.py:36 +msgid "Encrypted" +msgstr "" + +#: settings/models.py:158 +msgid "Can change email setting" +msgstr "" + +#: settings/models.py:159 +msgid "Can change auth setting" +msgstr "" + +#: settings/models.py:160 +msgid "Can change system msg sub setting" +msgstr "" + +#: settings/models.py:161 +msgid "Can change sms setting" +msgstr "" + +#: settings/models.py:162 +msgid "Can change security setting" +msgstr "" + +#: settings/models.py:163 +msgid "Can change clean setting" +msgstr "" + +#: settings/models.py:164 +msgid "Can change interface setting" +msgstr "" + +#: settings/models.py:165 +msgid "Can change license setting" +msgstr "" + +#: settings/models.py:166 +msgid "Can change terminal setting" +msgstr "" + +#: settings/models.py:167 +msgid "Can change other setting" +msgstr "" + +#: settings/serializers/auth/base.py:12 +msgid "CAS Auth" +msgstr "" + +#: settings/serializers/auth/base.py:13 +msgid "OPENID Auth" +msgstr "" + +#: settings/serializers/auth/base.py:14 +msgid "RADIUS Auth" +msgstr "" + +#: settings/serializers/auth/base.py:15 +msgid "DingTalk Auth" +msgstr "" + +#: settings/serializers/auth/base.py:16 +msgid "FeiShu Auth" +msgstr "" + +#: settings/serializers/auth/base.py:17 +msgid "WeCom Auth" +msgstr "" + +#: settings/serializers/auth/base.py:18 +msgid "SSO Auth" +msgstr "" + +#: settings/serializers/auth/base.py:19 +msgid "SAML2 Auth" +msgstr "" + +#: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 +msgid "Forgot password url" +msgstr "" + +#: settings/serializers/auth/base.py:28 +msgid "Enable login redirect msg" +msgstr "" + +#: settings/serializers/auth/cas.py:10 +msgid "CAS" +msgstr "" + +#: settings/serializers/auth/cas.py:12 +msgid "Enable CAS Auth" +msgstr "" + +#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 +msgid "Server url" +msgstr "" + +#: settings/serializers/auth/cas.py:16 +msgid "Proxy server url" +msgstr "" + +#: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:55 +#: settings/serializers/auth/saml2.py:34 +msgid "Logout completely" +msgstr "" + +#: settings/serializers/auth/cas.py:23 +msgid "Username attr" +msgstr "" + +#: settings/serializers/auth/cas.py:26 +msgid "Enable attributes map" +msgstr "" + +#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 +msgid "Rename attr" +msgstr "" + +#: settings/serializers/auth/cas.py:29 +msgid "Create user if not" +msgstr "" + +#: settings/serializers/auth/dingtalk.py:15 +msgid "Enable DingTalk Auth" +msgstr "" + +#: settings/serializers/auth/feishu.py:14 +msgid "Enable FeiShu Auth" +msgstr "" + +#: settings/serializers/auth/ldap.py:39 +msgid "LDAP" +msgstr "" + +#: settings/serializers/auth/ldap.py:42 +msgid "LDAP server" +msgstr "" + +#: settings/serializers/auth/ldap.py:43 +msgid "eg: ldap://localhost:389" +msgstr "" + +#: settings/serializers/auth/ldap.py:45 +msgid "Bind DN" +msgstr "" + +#: settings/serializers/auth/ldap.py:50 +msgid "User OU" +msgstr "" + +#: settings/serializers/auth/ldap.py:51 +msgid "Use | split multi OUs" +msgstr "" + +#: settings/serializers/auth/ldap.py:54 +msgid "User search filter" +msgstr "" + +#: settings/serializers/auth/ldap.py:55 +#, python-format +msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" +msgstr "" + +#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/oauth2.py:57 +#: settings/serializers/auth/oidc.py:37 +msgid "User attr map" +msgstr "" + +#: settings/serializers/auth/ldap.py:59 +msgid "" +"User attr map present how to map LDAP user attr to jumpserver, username,name," +"email is jumpserver attr" +msgstr "" + +#: settings/serializers/auth/ldap.py:77 +msgid "Connect timeout" +msgstr "" + +#: settings/serializers/auth/ldap.py:79 +msgid "Search paged size" +msgstr "" + +#: settings/serializers/auth/ldap.py:81 +msgid "Enable LDAP auth" +msgstr "" + +#: settings/serializers/auth/oauth2.py:19 +msgid "OAuth2" +msgstr "" + +#: settings/serializers/auth/oauth2.py:22 +msgid "Enable OAuth2 Auth" +msgstr "" + +#: settings/serializers/auth/oauth2.py:25 +msgid "Logo" +msgstr "" + +#: settings/serializers/auth/oauth2.py:28 +msgid "Service provider" +msgstr "" + +#: settings/serializers/auth/oauth2.py:31 settings/serializers/auth/oidc.py:19 +msgid "Client Id" +msgstr "" + +#: settings/serializers/auth/oauth2.py:34 settings/serializers/auth/oidc.py:22 +#: xpack/plugins/cloud/serializers/account_attrs.py:38 +msgid "Client Secret" +msgstr "" + +#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 +msgid "Provider auth endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 +msgid "Provider token endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:46 settings/serializers/auth/oidc.py:30 +msgid "Client authentication method" +msgstr "" + +#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 +msgid "Provider userinfo endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 +msgid "Provider end session endpoint" +msgstr "" + +#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 +#: settings/serializers/auth/saml2.py:35 +msgid "Always update user" +msgstr "" + +#: settings/serializers/auth/oidc.py:12 +msgid "OIDC" +msgstr "" + +#: settings/serializers/auth/oidc.py:16 +msgid "Base site url" +msgstr "" + +#: settings/serializers/auth/oidc.py:32 +msgid "Share session" +msgstr "" + +#: settings/serializers/auth/oidc.py:34 +msgid "Ignore ssl verification" +msgstr "" + +#: settings/serializers/auth/oidc.py:38 +msgid "" +"User attr map present how to map OpenID user attr to jumpserver, username," +"name,email is jumpserver attr" +msgstr "" + +#: settings/serializers/auth/oidc.py:46 +msgid "Use Keycloak" +msgstr "" + +#: settings/serializers/auth/oidc.py:52 +msgid "Realm name" +msgstr "" + +#: settings/serializers/auth/oidc.py:58 +msgid "Enable OPENID Auth" +msgstr "" + +#: settings/serializers/auth/oidc.py:60 +msgid "Provider endpoint" +msgstr "" + +#: settings/serializers/auth/oidc.py:69 +msgid "Provider jwks endpoint" +msgstr "" + +#: settings/serializers/auth/oidc.py:78 +msgid "Provider sign alg" +msgstr "" + +#: settings/serializers/auth/oidc.py:81 +msgid "Provider sign key" +msgstr "" + +#: settings/serializers/auth/oidc.py:83 +msgid "Scopes" +msgstr "" + +#: settings/serializers/auth/oidc.py:85 +msgid "Id token max age" +msgstr "" + +#: settings/serializers/auth/oidc.py:88 +msgid "Id token include claims" +msgstr "" + +#: settings/serializers/auth/oidc.py:90 +msgid "Use state" +msgstr "" + +#: settings/serializers/auth/oidc.py:91 +msgid "Use nonce" +msgstr "" + +#: settings/serializers/auth/radius.py:13 +msgid "Radius" +msgstr "" + +#: settings/serializers/auth/radius.py:15 +msgid "Enable Radius Auth" +msgstr "" + +#: settings/serializers/auth/radius.py:21 +msgid "OTP in Radius" +msgstr "" + +#: settings/serializers/auth/saml2.py:11 +msgid "SAML2" +msgstr "" + +#: settings/serializers/auth/saml2.py:14 +msgid "Enable SAML2 Auth" +msgstr "" + +#: settings/serializers/auth/saml2.py:17 +msgid "IDP metadata URL" +msgstr "" + +#: settings/serializers/auth/saml2.py:20 +msgid "IDP metadata XML" +msgstr "" + +#: settings/serializers/auth/saml2.py:23 +msgid "SP advanced settings" +msgstr "" + +#: settings/serializers/auth/saml2.py:27 +msgid "SP private key" +msgstr "" + +#: settings/serializers/auth/saml2.py:31 +msgid "SP cert" +msgstr "" + +#: settings/serializers/auth/sms.py:15 +msgid "Enable SMS" +msgstr "" + +#: settings/serializers/auth/sms.py:17 +msgid "SMS provider / Protocol" +msgstr "" + +#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:45 +#: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 +#: settings/serializers/auth/sms.py:73 settings/serializers/email.py:68 +msgid "Signature" +msgstr "" + +#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:46 +#: settings/serializers/auth/sms.py:54 settings/serializers/auth/sms.py:63 +msgid "Template code" +msgstr "" + +#: settings/serializers/auth/sms.py:31 +msgid "Test phone" +msgstr "" + +#: settings/serializers/auth/sms.py:60 +msgid "App Access Address" +msgstr "" + +#: settings/serializers/auth/sms.py:61 +msgid "Signature channel number" +msgstr "" + +#: settings/serializers/auth/sms.py:69 +msgid "Enterprise code(SP id)" +msgstr "" + +#: settings/serializers/auth/sms.py:70 +msgid "Shared secret(Shared secret)" +msgstr "" + +#: settings/serializers/auth/sms.py:71 +msgid "Original number(Src id)" +msgstr "" + +#: settings/serializers/auth/sms.py:72 +msgid "Business type(Service id)" +msgstr "" + +#: settings/serializers/auth/sms.py:75 +msgid "Template" +msgstr "" + +#: settings/serializers/auth/sms.py:76 +#, python-brace-format +msgid "" +"Template need contain {code} and Signature + template length does not exceed " +"67 words. For example, your verification code is {code}, which is valid for " +"5 minutes. Please do not disclose it to others." +msgstr "" + +#: settings/serializers/auth/sms.py:85 +#, python-brace-format +msgid "The template needs to contain {code}" +msgstr "" + +#: settings/serializers/auth/sms.py:88 +msgid "Signature + Template must not exceed 65 words" +msgstr "" + +#: settings/serializers/auth/sso.py:13 +msgid "Enable SSO auth" +msgstr "" + +#: settings/serializers/auth/sso.py:14 +msgid "Other service can using SSO token login to JumpServer without password" +msgstr "" + +#: settings/serializers/auth/sso.py:17 +msgid "SSO auth key TTL" +msgstr "" + +#: settings/serializers/auth/sso.py:17 +#: xpack/plugins/cloud/serializers/account_attrs.py:176 +msgid "Unit: second" +msgstr "" + +#: settings/serializers/auth/wecom.py:15 +msgid "Enable WeCom Auth" +msgstr "" + +#: settings/serializers/basic.py:9 +msgid "Subject" +msgstr "" + +#: settings/serializers/basic.py:13 +msgid "More url" +msgstr "" + +#: settings/serializers/basic.py:30 +msgid "Site url" +msgstr "" + +#: settings/serializers/basic.py:31 +msgid "eg: http://dev.jumpserver.org:8080" +msgstr "" + +#: settings/serializers/basic.py:34 +msgid "User guide url" +msgstr "" + +#: settings/serializers/basic.py:35 +msgid "User first login update profile done redirect to it" +msgstr "" + +#: settings/serializers/basic.py:39 +msgid "" +"The forgot password url on login page, If you use ldap or cas external " +"authentication, you can set it" +msgstr "" + +#: settings/serializers/basic.py:43 +msgid "Global organization name" +msgstr "" + +#: settings/serializers/basic.py:44 +msgid "The name of global organization to display" +msgstr "" + +#: settings/serializers/basic.py:46 +msgid "Enable announcement" +msgstr "" + +#: settings/serializers/basic.py:47 +msgid "Announcement" +msgstr "" + +#: settings/serializers/basic.py:48 +msgid "Enable tickets" +msgstr "" + +#: settings/serializers/cleaning.py:8 +msgid "Period clean" +msgstr "" + +#: settings/serializers/cleaning.py:12 +msgid "Login log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 +#: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 +#: settings/serializers/cleaning.py:28 +msgid "Unit: day" +msgstr "" + +#: settings/serializers/cleaning.py:16 +msgid "Task log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:20 +msgid "Operate log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:24 +msgid "FTP log keep days" +msgstr "" + +#: settings/serializers/cleaning.py:28 +msgid "Cloud sync record keep days" +msgstr "" + +#: settings/serializers/cleaning.py:31 +msgid "Session keep duration" +msgstr "" + +#: settings/serializers/cleaning.py:32 +msgid "" +"Unit: days, Session, record, command will be delete if more than duration, " +"only in database" +msgstr "" + +#: settings/serializers/email.py:21 +msgid "SMTP host" +msgstr "" + +#: settings/serializers/email.py:22 +msgid "SMTP port" +msgstr "" + +#: settings/serializers/email.py:23 +msgid "SMTP account" +msgstr "" + +#: settings/serializers/email.py:25 +msgid "SMTP password" +msgstr "" + +#: settings/serializers/email.py:26 +msgid "Tips: Some provider use token except password" +msgstr "" + +#: settings/serializers/email.py:29 +msgid "Send user" +msgstr "" + +#: settings/serializers/email.py:30 +msgid "Tips: Send mail account, default SMTP account as the send account" +msgstr "" + +#: settings/serializers/email.py:33 +msgid "Test recipient" +msgstr "" + +#: settings/serializers/email.py:34 +msgid "Tips: Used only as a test mail recipient" +msgstr "" + +#: settings/serializers/email.py:38 +msgid "If SMTP port is 465, may be select" +msgstr "" + +#: settings/serializers/email.py:41 +msgid "Use TLS" +msgstr "" + +#: settings/serializers/email.py:42 +msgid "If SMTP port is 587, may be select" +msgstr "" + +#: settings/serializers/email.py:45 +msgid "Subject prefix" +msgstr "" + +#: settings/serializers/email.py:54 +msgid "Create user email subject" +msgstr "" + +#: settings/serializers/email.py:55 +msgid "" +"Tips: When creating a user, send the subject of the email (eg:Create account " +"successfully)" +msgstr "" + +#: settings/serializers/email.py:59 +msgid "Create user honorific" +msgstr "" + +#: settings/serializers/email.py:60 +msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" +msgstr "" + +#: settings/serializers/email.py:64 +msgid "Create user email content" +msgstr "" + +#: settings/serializers/email.py:65 +#, python-brace-format +msgid "" +"Tips: When creating a user, send the content of the email, support " +"{username} {name} {email} label" +msgstr "" + +#: settings/serializers/email.py:69 +msgid "Tips: Email signature (eg:jumpserver)" +msgstr "" + +#: settings/serializers/other.py:6 +msgid "More..." +msgstr "" + +#: settings/serializers/other.py:9 +msgid "Email suffix" +msgstr "" + +#: settings/serializers/other.py:10 +msgid "" +"This is used by default if no email is returned during SSO authentication" +msgstr "" + +#: settings/serializers/other.py:14 +msgid "OTP issuer name" +msgstr "" + +#: settings/serializers/other.py:18 +msgid "OTP valid window" +msgstr "" + +#: settings/serializers/other.py:23 +msgid "CMD" +msgstr "" + +#: settings/serializers/other.py:24 +msgid "PowerShell" +msgstr "" + +#: settings/serializers/other.py:26 +msgid "Shell (Windows)" +msgstr "" + +#: settings/serializers/other.py:27 +msgid "The shell type used when Windows assets perform ansible tasks" +msgstr "" + +#: settings/serializers/other.py:31 +msgid "Perm ungroup node" +msgstr "" + +#: settings/serializers/other.py:32 +msgid "Perm single to ungroup node" +msgstr "" + +#: settings/serializers/other.py:37 +msgid "Ticket authorize default time" +msgstr "" + +#: settings/serializers/other.py:40 +msgid "day" +msgstr "" + +#: settings/serializers/other.py:40 +msgid "hour" +msgstr "" + +#: settings/serializers/other.py:41 +msgid "Ticket authorize default time unit" +msgstr "" + +#: settings/serializers/other.py:44 +msgid "Help Docs URL" +msgstr "" + +#: settings/serializers/other.py:45 +msgid "default: http://docs.jumpserver.org" +msgstr "" + +#: settings/serializers/other.py:49 +msgid "Help Support URL" +msgstr "" + +#: settings/serializers/other.py:50 +msgid "default: http://www.jumpserver.org/support/" +msgstr "" + +#: settings/serializers/security.py:10 +msgid "Password minimum length" +msgstr "" + +#: settings/serializers/security.py:14 +msgid "Admin user password minimum length" +msgstr "" + +#: settings/serializers/security.py:17 +msgid "Must contain capital" +msgstr "" + +#: settings/serializers/security.py:20 +msgid "Must contain lowercase" +msgstr "" + +#: settings/serializers/security.py:23 +msgid "Must contain numeric" +msgstr "" + +#: settings/serializers/security.py:26 +msgid "Must contain special" +msgstr "" + +#: settings/serializers/security.py:31 +msgid "" +"Unit: minute, If the user has failed to log in for a limited number of " +"times, no login is allowed during this time interval." +msgstr "" + +#: settings/serializers/security.py:40 +msgid "All users" +msgstr "" + +#: settings/serializers/security.py:41 +msgid "Only admin users" +msgstr "" + +#: settings/serializers/security.py:43 +msgid "Global MFA auth" +msgstr "" + +#: settings/serializers/security.py:47 +msgid "Third-party login users perform MFA authentication" +msgstr "" + +#: settings/serializers/security.py:48 +msgid "The third-party login modes include OIDC, CAS, and SAML2" +msgstr "" + +#: settings/serializers/security.py:52 +msgid "Limit the number of user login failures" +msgstr "" + +#: settings/serializers/security.py:56 +msgid "Block user login interval" +msgstr "" + +#: settings/serializers/security.py:61 +msgid "Limit the number of IP login failures" +msgstr "" + +#: settings/serializers/security.py:65 +msgid "Block IP login interval" +msgstr "" + +#: settings/serializers/security.py:69 +msgid "Login IP White List" +msgstr "" + +#: settings/serializers/security.py:74 +msgid "Login IP Black List" +msgstr "" + +#: settings/serializers/security.py:80 +msgid "User password expiration" +msgstr "" + +#: settings/serializers/security.py:82 +msgid "" +"Unit: day, If the user does not update the password during the time, the " +"user password will expire failure;The password expiration reminder mail will " +"be automatic sent to the user by system within 5 days (daily) before the " +"password expires" +msgstr "" + +#: settings/serializers/security.py:89 +msgid "Number of repeated historical passwords" +msgstr "" + +#: settings/serializers/security.py:91 +msgid "" +"Tip: When the user resets the password, it cannot be the previous n " +"historical passwords of the user" +msgstr "" + +#: settings/serializers/security.py:96 +msgid "Only single device login" +msgstr "" + +#: settings/serializers/security.py:97 +msgid "Next device login, pre login will be logout" +msgstr "" + +#: settings/serializers/security.py:100 +msgid "Only exist user login" +msgstr "" + +#: settings/serializers/security.py:101 +msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" +msgstr "" + +#: settings/serializers/security.py:104 +msgid "Only from source login" +msgstr "" + +#: settings/serializers/security.py:105 +msgid "Only log in from the user source property" +msgstr "" + +#: settings/serializers/security.py:109 +msgid "MFA verify TTL" +msgstr "" + +#: settings/serializers/security.py:111 +msgid "" +"Unit: second, The verification MFA takes effect only when you view the " +"account password" +msgstr "" + +#: settings/serializers/security.py:116 +msgid "Enable Login dynamic code" +msgstr "" + +#: settings/serializers/security.py:117 +msgid "" +"The password and additional code are sent to a third party authentication " +"system for verification" +msgstr "" + +#: settings/serializers/security.py:122 +msgid "MFA in login page" +msgstr "" + +#: settings/serializers/security.py:123 +msgid "Eu security regulations(GDPR) require MFA to be on the login page" +msgstr "" + +#: settings/serializers/security.py:126 +msgid "Enable Login captcha" +msgstr "" + +#: settings/serializers/security.py:127 +msgid "Enable captcha to prevent robot authentication" +msgstr "" + +#: settings/serializers/security.py:146 +msgid "Security" +msgstr "" + +#: settings/serializers/security.py:149 +msgid "Enable terminal register" +msgstr "" + +#: settings/serializers/security.py:151 +msgid "" +"Allow terminal register, after all terminal setup, you should disable this " +"for security" +msgstr "" + +#: settings/serializers/security.py:155 +msgid "Enable watermark" +msgstr "" + +#: settings/serializers/security.py:156 +msgid "Enabled, the web session and replay contains watermark information" +msgstr "" + +#: settings/serializers/security.py:160 +msgid "Connection max idle time" +msgstr "" + +#: settings/serializers/security.py:161 +msgid "If idle time more than it, disconnect connection Unit: minute" +msgstr "" + +#: settings/serializers/security.py:164 +msgid "Remember manual auth" +msgstr "" + +#: settings/serializers/security.py:167 +msgid "Enable change auth secure mode" +msgstr "" + +#: settings/serializers/security.py:170 +msgid "Insecure command alert" +msgstr "" + +#: settings/serializers/security.py:173 +msgid "Email recipient" +msgstr "" + +#: settings/serializers/security.py:174 +msgid "Multiple user using , split" +msgstr "" + +#: settings/serializers/security.py:177 +msgid "Batch command execution" +msgstr "" + +#: settings/serializers/security.py:178 +msgid "Allow user run batch command or not using ansible" +msgstr "" + +#: settings/serializers/security.py:181 +msgid "Session share" +msgstr "" + +#: settings/serializers/security.py:182 +msgid "Enabled, Allows user active session to be shared with other users" +msgstr "" + +#: settings/serializers/security.py:185 +msgid "Remote Login Protection" +msgstr "" + +#: settings/serializers/security.py:187 +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 " +"a remote login reminder" +msgstr "" + +#: settings/serializers/terminal.py:15 +msgid "Auto" +msgstr "" + +#: settings/serializers/terminal.py:21 +msgid "Password auth" +msgstr "" + +#: settings/serializers/terminal.py:23 +msgid "Public key auth" +msgstr "" + +#: settings/serializers/terminal.py:24 +msgid "" +"Tips: If use other auth method, like AD/LDAP, you should disable this to " +"avoid being able to log in after deleting" +msgstr "" + +#: settings/serializers/terminal.py:28 +msgid "List sort by" +msgstr "" + +#: settings/serializers/terminal.py:31 +msgid "List page size" +msgstr "" + +#: settings/serializers/terminal.py:34 +msgid "Telnet login regex" +msgstr "" + +#: settings/serializers/terminal.py:35 +msgid "" +"Tips: The login success message varies with devices. if you cannot log in to " +"the device through Telnet, set this parameter" +msgstr "" + +#: settings/serializers/terminal.py:38 +msgid "Enable database proxy" +msgstr "" + +#: settings/serializers/terminal.py:39 +msgid "Enable Razor" +msgstr "" + +#: settings/serializers/terminal.py:40 +msgid "Enable SSH Client" +msgstr "" + +#: settings/serializers/terminal.py:51 +msgid "Default graphics resolution" +msgstr "" + +#: settings/serializers/terminal.py:52 +msgid "" +"Tip: Default resolution to use when connecting graphical assets in Luna pages" +msgstr "" + +#: settings/utils/ldap.py:467 +msgid "ldap:// or ldaps:// protocol is used." +msgstr "" + +#: settings/utils/ldap.py:478 +msgid "Host or port is disconnected: {}" +msgstr "" + +#: settings/utils/ldap.py:480 +msgid "The port is not the port of the LDAP service: {}" +msgstr "" + +#: settings/utils/ldap.py:482 +msgid "Please add certificate: {}" +msgstr "" + +#: settings/utils/ldap.py:486 settings/utils/ldap.py:513 +#: settings/utils/ldap.py:543 settings/utils/ldap.py:571 +msgid "Unknown error: {}" +msgstr "" + +#: settings/utils/ldap.py:500 +msgid "Bind DN or Password incorrect" +msgstr "" + +#: settings/utils/ldap.py:507 +msgid "Please enter Bind DN: {}" +msgstr "" + +#: settings/utils/ldap.py:509 +msgid "Please enter Password: {}" +msgstr "" + +#: settings/utils/ldap.py:511 +msgid "Please enter correct Bind DN and Password: {}" +msgstr "" + +#: settings/utils/ldap.py:529 +msgid "Invalid User OU or User search filter: {}" +msgstr "" + +#: settings/utils/ldap.py:560 +msgid "LDAP User attr map not include: {}" +msgstr "" + +#: settings/utils/ldap.py:567 +msgid "LDAP User attr map is not dict" +msgstr "" + +#: settings/utils/ldap.py:586 +msgid "LDAP authentication is not enabled" +msgstr "" + +#: settings/utils/ldap.py:604 +msgid "Error (Invalid LDAP server): {}" +msgstr "" + +#: settings/utils/ldap.py:606 +msgid "Error (Invalid Bind DN): {}" +msgstr "" + +#: settings/utils/ldap.py:608 +msgid "Error (Invalid LDAP User attr map): {}" +msgstr "" + +#: settings/utils/ldap.py:610 +msgid "Error (Invalid User OU or User search filter): {}" +msgstr "" + +#: settings/utils/ldap.py:612 +msgid "Error (Not enabled LDAP authentication): {}" +msgstr "" + +#: settings/utils/ldap.py:614 +msgid "Error (Unknown): {}" +msgstr "" + +#: settings/utils/ldap.py:617 +msgid "Succeed: Match {} s user" +msgstr "" + +#: settings/utils/ldap.py:650 +msgid "Authentication failed (configuration incorrect): {}" +msgstr "" + +#: settings/utils/ldap.py:654 +msgid "Authentication failed (username or password incorrect): {}" +msgstr "" + +#: settings/utils/ldap.py:656 +msgid "Authentication failed (Unknown): {}" +msgstr "" + +#: settings/utils/ldap.py:659 +msgid "Authentication success: {}" +msgstr "" + +#: templates/_csv_import_export.html:8 +msgid "Export" +msgstr "" + +#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 +msgid "Import" +msgstr "" + +#: templates/_csv_import_modal.html:12 +msgid "Download the imported template or use the exported CSV file format" +msgstr "" + +#: templates/_csv_import_modal.html:13 +msgid "Download the import template" +msgstr "" + +#: templates/_csv_import_modal.html:17 templates/_csv_update_modal.html:17 +msgid "Select the CSV file to import" +msgstr "" + +#: templates/_csv_import_modal.html:39 templates/_csv_update_modal.html:42 +msgid "Please select file" +msgstr "" + +#: templates/_csv_update_modal.html:12 +msgid "Download the update template or use the exported CSV file format" +msgstr "" + +#: templates/_csv_update_modal.html:13 +msgid "Download the update template" +msgstr "" + +#: templates/_header_bar.html:12 +msgid "Help" +msgstr "" + +#: templates/_header_bar.html:19 +msgid "Docs" +msgstr "" + +#: templates/_header_bar.html:25 +msgid "Commercial support" +msgstr "" + +#: templates/_header_bar.html:76 users/forms/profile.py:44 +msgid "Profile" +msgstr "" + +#: templates/_header_bar.html:79 +msgid "Admin page" +msgstr "" + +#: templates/_header_bar.html:81 +msgid "User page" +msgstr "" + +#: templates/_header_bar.html:84 +msgid "API Key" +msgstr "" + +#: templates/_header_bar.html:85 +msgid "Logout" +msgstr "" + +#: templates/_message.html:6 +msgid "" +"\n" +" Your account has expired, please contact the administrator.\n" +" " +msgstr "" + +#: templates/_message.html:13 +msgid "Your account will at" +msgstr "" + +#: templates/_message.html:13 templates/_message.html:30 +msgid "expired. " +msgstr "" + +#: templates/_message.html:23 +#, python-format +msgid "" +"\n" +" Your password has expired, please click this link update password.\n" +" " +msgstr "" + +#: templates/_message.html:30 +msgid "Your password will at" +msgstr "" + +#: templates/_message.html:31 +#, python-format +msgid "" +"\n" +" please click this " +"link to update your password.\n" +" " +msgstr "" + +#: templates/_message.html:43 +#, python-format +msgid "" +"\n" +" Your information was incomplete. Please click this link to complete your information.\n" +" " +msgstr "" + +#: templates/_message.html:56 +#, python-format +msgid "" +"\n" +" Your ssh public key not set or expired. Please click this link to update\n" +" " +msgstr "" + +#: templates/_mfa_login_field.html:28 +msgid "Send verification code" +msgstr "" + +#: templates/_mfa_login_field.html:106 +#: users/templates/users/forgot_password.html:129 +msgid "Wait: " +msgstr "" + +#: templates/_mfa_login_field.html:116 +#: users/templates/users/forgot_password.html:145 +msgid "The verification code has been sent" +msgstr "" + +#: templates/_without_nav_base.html:26 +msgid "Home page" +msgstr "" + +#: templates/resource_download.html:18 templates/resource_download.html:31 +msgid "Client" +msgstr "" + +#: templates/resource_download.html:20 +msgid "" +"JumpServer Client, currently used to launch the client, now only support " +"launch RDP SSH client, The Telnet client will next" +msgstr "" + +#: templates/resource_download.html:31 +msgid "Microsoft" +msgstr "" + +#: templates/resource_download.html:31 +msgid "Official" +msgstr "" + +#: templates/resource_download.html:33 +msgid "" +"macOS needs to download the client to connect RDP asset, which comes with " +"Windows" +msgstr "" + +#: templates/resource_download.html:42 +msgid "Windows Remote application publisher tools" +msgstr "" + +#: templates/resource_download.html:43 +msgid "" +"OpenSSH is a program used to connect remote applications in the Windows " +"Remote Application Publisher" +msgstr "" + +#: templates/resource_download.html:48 +msgid "" +"Jmservisor is the program used to pull up remote applications in Windows " +"Remote Application publisher" +msgstr "" + +#: templates/resource_download.html:57 +msgid "Offline video player" +msgstr "" + +#: terminal/api/component/endpoint.py:31 +msgid "Not found protocol query params" +msgstr "" + +#: terminal/api/component/storage.py:28 +msgid "Deleting the default storage is not allowed" +msgstr "" + +#: terminal/api/component/storage.py:31 +msgid "Cannot delete storage that is being used" +msgstr "" + +#: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 +msgid "Command storages" +msgstr "" + +#: terminal/api/component/storage.py:79 +msgid "Invalid" +msgstr "" + +#: terminal/api/component/storage.py:119 +msgid "Test failure: {}" +msgstr "" + +#: terminal/api/component/storage.py:122 +msgid "Test successful" +msgstr "" + +#: terminal/api/component/storage.py:124 +msgid "Test failure: Account invalid" +msgstr "" + +#: terminal/api/component/terminal.py:38 +msgid "Have online sessions" +msgstr "" + +#: terminal/api/session/session.py:217 +msgid "Session does not exist: {}" +msgstr "" + +#: terminal/api/session/session.py:220 +msgid "Session is finished or the protocol not supported" +msgstr "" + +#: terminal/api/session/session.py:233 +msgid "User does not have permission" +msgstr "" + +#: terminal/api/session/sharing.py:29 +msgid "Secure session sharing settings is disabled" +msgstr "" + +#: terminal/apps.py:9 +msgid "Terminals" +msgstr "" + +#: terminal/backends/command/models.py:16 +msgid "Ordinary" +msgstr "" + +#: terminal/backends/command/models.py:17 +msgid "Dangerous" +msgstr "" + +#: terminal/backends/command/models.py:23 +msgid "Input" +msgstr "" + +#: terminal/backends/command/models.py:24 +#: terminal/backends/command/serializers.py:38 +msgid "Output" +msgstr "" + +#: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 +#: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 +#: terminal/templates/terminal/_msg_command_alert.html:10 +#: tickets/models/ticket/command_confirm.py:17 +msgid "Session" +msgstr "" + +#: terminal/backends/command/models.py:26 +#: terminal/backends/command/serializers.py:18 +msgid "Risk level" +msgstr "" + +#: terminal/backends/command/serializers.py:16 +msgid "Session ID" +msgstr "" + +#: terminal/backends/command/serializers.py:37 +msgid "Account " +msgstr "" + +#: terminal/backends/command/serializers.py:39 +msgid "Risk level display" +msgstr "" + +#: terminal/backends/command/serializers.py:40 +msgid "Timestamp" +msgstr "" + +#: terminal/backends/command/serializers.py:42 +#: terminal/models/component/terminal.py:85 +msgid "Remote Address" +msgstr "" + +#: terminal/const.py:37 +msgid "Critical" +msgstr "" + +#: terminal/const.py:38 +msgid "High" +msgstr "" + +#: terminal/const.py:39 users/templates/users/reset_password.html:50 +msgid "Normal" +msgstr "" + +#: terminal/const.py:40 +msgid "Offline" +msgstr "" + +#: terminal/const.py:81 terminal/const.py:82 terminal/const.py:83 +#: terminal/const.py:84 terminal/const.py:85 +msgid "DB Client" +msgstr "" + +#: terminal/exceptions.py:8 +msgid "Bulk create not support" +msgstr "" + +#: terminal/exceptions.py:13 +msgid "Storage is invalid" +msgstr "" + +#: terminal/models/applet/applet.py:23 +msgid "Author" +msgstr "" + +#: terminal/models/applet/applet.py:27 +msgid "Tags" +msgstr "" + +#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 +msgid "Hosts" +msgstr "" + +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 +msgid "Applet" +msgstr "" + +#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 +msgid "Deploy options" +msgstr "" + +#: terminal/models/applet/host.py:19 +msgid "Inited" +msgstr "" + +#: terminal/models/applet/host.py:20 +msgid "Date inited" +msgstr "" + +#: terminal/models/applet/host.py:21 +msgid "Date synced" +msgstr "" + +#: terminal/models/applet/host.py:102 +msgid "Hosting" +msgstr "" + +#: terminal/models/applet/host.py:103 +msgid "Initial" +msgstr "" + +#: terminal/models/component/endpoint.py:15 +msgid "HTTPS Port" +msgstr "" + +#: terminal/models/component/endpoint.py:16 +msgid "HTTP Port" +msgstr "" + +#: terminal/models/component/endpoint.py:17 +msgid "SSH Port" +msgstr "" + +#: terminal/models/component/endpoint.py:18 +msgid "RDP Port" +msgstr "" + +#: terminal/models/component/endpoint.py:25 +#: terminal/models/component/endpoint.py:94 terminal/serializers/endpoint.py:57 +#: 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 +msgid "Endpoint" +msgstr "" + +#: terminal/models/component/endpoint.py:87 +msgid "IP group" +msgstr "" + +#: terminal/models/component/endpoint.py:99 +msgid "Endpoint rule" +msgstr "" + +#: terminal/models/component/status.py:14 +msgid "Session Online" +msgstr "" + +#: terminal/models/component/status.py:15 +msgid "CPU Load" +msgstr "" + +#: terminal/models/component/status.py:16 +msgid "Memory Used" +msgstr "" + +#: terminal/models/component/status.py:17 +msgid "Disk Used" +msgstr "" + +#: terminal/models/component/status.py:18 +msgid "Connections" +msgstr "" + +#: terminal/models/component/status.py:19 +msgid "Threads" +msgstr "" + +#: terminal/models/component/status.py:20 +msgid "Boot Time" +msgstr "" + +#: terminal/models/component/storage.py:27 +msgid "Default storage" +msgstr "" + +#: terminal/models/component/storage.py:140 +#: terminal/models/component/terminal.py:86 +msgid "Command storage" +msgstr "" + +#: terminal/models/component/storage.py:200 +#: terminal/models/component/terminal.py:87 +msgid "Replay storage" +msgstr "" + +#: terminal/models/component/terminal.py:83 +msgid "type" +msgstr "" + +#: terminal/models/component/terminal.py:88 +msgid "Application User" +msgstr "" + +#: terminal/models/component/terminal.py:161 +msgid "Can view terminal config" +msgstr "" + +#: terminal/models/session/command.py:66 +msgid "Command record" +msgstr "" + +#: terminal/models/session/replay.py:12 +msgid "Session replay" +msgstr "" + +#: terminal/models/session/replay.py:14 +msgid "Can upload session replay" +msgstr "" + +#: terminal/models/session/replay.py:15 +msgid "Can download session replay" +msgstr "" + +#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 +msgid "Login from" +msgstr "" + +#: terminal/models/session/session.py:40 +msgid "Replay" +msgstr "" + +#: terminal/models/session/session.py:44 +msgid "Date end" +msgstr "" + +#: terminal/models/session/session.py:236 +msgid "Session record" +msgstr "" + +#: terminal/models/session/session.py:238 +msgid "Can monitor session" +msgstr "" + +#: terminal/models/session/session.py:239 +msgid "Can share session" +msgstr "" + +#: terminal/models/session/session.py:240 +msgid "Can terminate session" +msgstr "" + +#: terminal/models/session/session.py:241 +msgid "Can validate session action perm" +msgstr "" + +#: terminal/models/session/sharing.py:31 +msgid "Expired time (min)" +msgstr "" + +#: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 +msgid "Session sharing" +msgstr "" + +#: terminal/models/session/sharing.py:39 +msgid "Can add super session sharing" +msgstr "" + +#: terminal/models/session/sharing.py:66 +msgid "Link not active" +msgstr "" + +#: terminal/models/session/sharing.py:68 +msgid "Link expired" +msgstr "" + +#: terminal/models/session/sharing.py:70 +msgid "User not allowed to join" +msgstr "" + +#: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 +msgid "Joiner" +msgstr "" + +#: terminal/models/session/sharing.py:90 +msgid "Date joined" +msgstr "" + +#: terminal/models/session/sharing.py:93 +msgid "Date left" +msgstr "" + +#: terminal/models/session/sharing.py:116 +msgid "Session join record" +msgstr "" + +#: terminal/models/session/sharing.py:132 +msgid "Invalid verification code" +msgstr "" + +#: terminal/notifications.py:22 +msgid "Sessions" +msgstr "" + +#: terminal/notifications.py:68 +msgid "Danger command alert" +msgstr "" + +#: terminal/notifications.py:95 terminal/notifications.py:143 +msgid "Level" +msgstr "" + +#: terminal/notifications.py:113 +msgid "Batch danger command alert" +msgstr "" + +#: terminal/serializers/applet.py:16 +msgid "Published" +msgstr "" + +#: terminal/serializers/applet.py:17 +msgid "Unpublished" +msgstr "" + +#: terminal/serializers/applet.py:18 +msgid "Not match" +msgstr "" + +#: terminal/serializers/applet.py:32 +msgid "Icon" +msgstr "" + +#: terminal/serializers/applet_host.py:21 +msgid "Per Session" +msgstr "" + +#: terminal/serializers/applet_host.py:22 +msgid "Per Device" +msgstr "" + +#: terminal/serializers/applet_host.py:28 +msgid "RDS Licensing" +msgstr "" + +#: terminal/serializers/applet_host.py:29 +msgid "RDS License Server" +msgstr "" + +#: terminal/serializers/applet_host.py:30 +msgid "RDS Licensing Mode" +msgstr "" + +#: terminal/serializers/applet_host.py:32 +msgid "RDS fSingleSessionPerUser" +msgstr "" + +#: terminal/serializers/applet_host.py:33 +msgid "RDS Max Disconnection Time" +msgstr "" + +#: terminal/serializers/applet_host.py:34 +msgid "RDS Remote App Logoff Time Limit" +msgstr "" + +#: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 +msgid "Load status" +msgstr "" + +#: terminal/serializers/endpoint.py:14 +msgid "Magnus listen db port" +msgstr "" + +#: terminal/serializers/endpoint.py:17 +msgid "Magnus Listen port range" +msgstr "" + +#: terminal/serializers/endpoint.py:19 +msgid "" +"The range of ports that Magnus listens on is modified in the configuration " +"file" +msgstr "" + +#: terminal/serializers/endpoint.py:51 +msgid "" +"If asset IP addresses under different endpoints conflict, use asset labels" +msgstr "" + +#: terminal/serializers/session.py:17 terminal/serializers/session.py:42 +msgid "Terminal display" +msgstr "" + +#: terminal/serializers/session.py:33 +msgid "User ID" +msgstr "" + +#: terminal/serializers/session.py:34 +msgid "Asset ID" +msgstr "" + +#: terminal/serializers/session.py:35 +msgid "Login from display" +msgstr "" + +#: terminal/serializers/session.py:37 +msgid "Can replay" +msgstr "" + +#: terminal/serializers/session.py:38 +msgid "Can join" +msgstr "" + +#: terminal/serializers/session.py:39 +msgid "Terminal ID" +msgstr "" + +#: terminal/serializers/session.py:40 +msgid "Is finished" +msgstr "" + +#: terminal/serializers/session.py:41 +msgid "Can terminate" +msgstr "" + +#: terminal/serializers/session.py:47 +msgid "Command amount" +msgstr "" + +#: terminal/serializers/storage.py:20 +msgid "Endpoint invalid: remove path `{}`" +msgstr "" + +#: terminal/serializers/storage.py:26 +msgid "Bucket" +msgstr "" + +#: terminal/serializers/storage.py:30 +#: xpack/plugins/cloud/serializers/account_attrs.py:17 +msgid "Access key id" +msgstr "" + +#: terminal/serializers/storage.py:34 +#: xpack/plugins/cloud/serializers/account_attrs.py:20 +msgid "Access key secret" +msgstr "" + +#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:219 +msgid "Region" +msgstr "" + +#: terminal/serializers/storage.py:109 +msgid "Container name" +msgstr "" + +#: terminal/serializers/storage.py:112 +msgid "Account key" +msgstr "" + +#: terminal/serializers/storage.py:115 +msgid "Endpoint suffix" +msgstr "" + +#: terminal/serializers/storage.py:135 +msgid "The address format is incorrect" +msgstr "" + +#: terminal/serializers/storage.py:142 +msgid "Host invalid" +msgstr "" + +#: terminal/serializers/storage.py:145 +msgid "Port invalid" +msgstr "" + +#: terminal/serializers/storage.py:160 +msgid "Index by date" +msgstr "" + +#: terminal/serializers/storage.py:161 +msgid "Whether to create an index by date" +msgstr "" + +#: terminal/serializers/storage.py:164 +msgid "Index" +msgstr "" + +#: terminal/serializers/storage.py:166 +msgid "Doc type" +msgstr "" + +#: terminal/serializers/storage.py:168 +msgid "Ignore Certificate Verification" +msgstr "" + +#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 +msgid "Not found" +msgstr "" + +#: terminal/templates/terminal/_msg_command_alert.html:10 +msgid "view" +msgstr "" + +#: terminal/utils/db_port_mapper.py:64 +msgid "" +"No available port is matched. The number of databases may have exceeded the " +"number of ports open to the database agent service, Contact the " +"administrator to open more ports." +msgstr "" + +#: terminal/utils/db_port_mapper.py:90 +msgid "" +"No ports can be used, check and modify the limit on the number of ports that " +"Magnus listens on in the configuration file." +msgstr "" + +#: terminal/utils/db_port_mapper.py:92 +msgid "All available port count: {}, Already use port count: {}" +msgstr "" + +#: tickets/apps.py:7 +msgid "Tickets" +msgstr "" + +#: tickets/const.py:9 +msgid "Apply for asset" +msgstr "" + +#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 +msgid "Open" +msgstr "" + +#: tickets/const.py:18 tickets/const.py:31 +msgid "Reopen" +msgstr "" + +#: tickets/const.py:19 tickets/const.py:32 +msgid "Approved" +msgstr "" + +#: tickets/const.py:20 tickets/const.py:33 +msgid "Rejected" +msgstr "" + +#: tickets/const.py:30 tickets/const.py:38 +msgid "Closed" +msgstr "" + +#: tickets/const.py:46 +msgid "Approve" +msgstr "" + +#: tickets/const.py:50 +msgid "One level" +msgstr "" + +#: tickets/const.py:51 +msgid "Two level" +msgstr "" + +#: tickets/const.py:55 +msgid "Org admin" +msgstr "" + +#: tickets/const.py:56 +msgid "Custom user" +msgstr "" + +#: tickets/const.py:57 +msgid "Super admin" +msgstr "" + +#: tickets/const.py:58 +msgid "Super admin and org admin" +msgstr "" + +#: tickets/errors.py:9 +msgid "Ticket already closed" +msgstr "" + +#: tickets/handlers/apply_asset.py:36 +msgid "" +"Created by the ticket ticket title: {} ticket applicant: {} ticket " +"processor: {} ticket ID: {}" +msgstr "" + +#: tickets/handlers/base.py:84 +msgid "Change field" +msgstr "" + +#: tickets/handlers/base.py:84 +msgid "Before change" +msgstr "" + +#: tickets/handlers/base.py:84 +msgid "After change" +msgstr "" + +#: tickets/handlers/base.py:96 +msgid "{} {} the ticket" +msgstr "" + +#: tickets/models/comment.py:14 +msgid "common" +msgstr "" + +#: tickets/models/comment.py:23 +msgid "User display name" +msgstr "" + +#: tickets/models/comment.py:24 +msgid "Body" +msgstr "" + +#: tickets/models/flow.py:20 tickets/models/flow.py:62 +#: tickets/models/ticket/general.py:39 +msgid "Approve level" +msgstr "" + +#: tickets/models/flow.py:25 tickets/serializers/flow.py:18 +msgid "Approve strategy" +msgstr "" + +#: tickets/models/flow.py:30 tickets/serializers/flow.py:20 +msgid "Assignees" +msgstr "" + +#: tickets/models/flow.py:34 +msgid "Ticket flow approval rule" +msgstr "" + +#: tickets/models/flow.py:67 +msgid "Ticket flow" +msgstr "" + +#: tickets/models/relation.py:10 +msgid "Ticket session relation" +msgstr "" + +#: tickets/models/ticket/apply_application.py:10 +#: tickets/models/ticket/apply_asset.py:13 +msgid "Permission name" +msgstr "" + +#: tickets/models/ticket/apply_application.py:19 +msgid "Apply applications" +msgstr "" + +#: tickets/models/ticket/apply_application.py:22 +msgid "Apply system users" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:9 +#: tickets/serializers/ticket/apply_asset.py:14 +msgid "Select at least one asset or node" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:14 +#: tickets/serializers/ticket/apply_asset.py:19 +msgid "Apply nodes" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:16 +#: tickets/serializers/ticket/apply_asset.py:18 +msgid "Apply assets" +msgstr "" + +#: tickets/models/ticket/apply_asset.py:17 +msgid "Apply accounts" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:10 +msgid "Run user" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:12 +msgid "Run asset" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:13 +msgid "Run command" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:14 +msgid "Run account" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:21 +msgid "From cmd filter" +msgstr "" + +#: tickets/models/ticket/command_confirm.py:25 +msgid "From cmd filter rule" +msgstr "" + +#: tickets/models/ticket/general.py:74 +msgid "Ticket step" +msgstr "" + +#: tickets/models/ticket/general.py:92 +msgid "Ticket assignee" +msgstr "" + +#: tickets/models/ticket/general.py:271 +msgid "Title" +msgstr "" + +#: tickets/models/ticket/general.py:287 +msgid "Applicant" +msgstr "" + +#: tickets/models/ticket/general.py:291 +msgid "TicketFlow" +msgstr "" + +#: tickets/models/ticket/general.py:294 +msgid "Approval step" +msgstr "" + +#: tickets/models/ticket/general.py:297 +msgid "Relation snapshot" +msgstr "" + +#: tickets/models/ticket/general.py:391 +msgid "Please try again" +msgstr "" + +#: tickets/models/ticket/general.py:424 +msgid "Super ticket" +msgstr "" + +#: tickets/models/ticket/login_asset_confirm.py:11 +msgid "Login user" +msgstr "" + +#: tickets/models/ticket/login_asset_confirm.py:14 +msgid "Login asset" +msgstr "" + +#: tickets/models/ticket/login_asset_confirm.py:17 +msgid "Login account" +msgstr "" + +#: tickets/models/ticket/login_confirm.py:12 +msgid "Login datetime" +msgstr "" + +#: tickets/notifications.py:63 +msgid "Ticket basic info" +msgstr "" + +#: tickets/notifications.py:64 +msgid "Ticket applied info" +msgstr "" + +#: tickets/notifications.py:109 +msgid "Your has a new ticket, applicant - {}" +msgstr "" + +#: tickets/notifications.py:113 +msgid "{}: New Ticket - {} ({})" +msgstr "" + +#: tickets/notifications.py:157 +msgid "Your ticket has been processed, processor - {}" +msgstr "" + +#: tickets/notifications.py:161 +msgid "Ticket has processed - {} ({})" +msgstr "" + +#: tickets/serializers/flow.py:21 +msgid "Assignees display" +msgstr "" + +#: tickets/serializers/flow.py:47 +msgid "Please select the Assignees" +msgstr "" + +#: tickets/serializers/flow.py:75 +msgid "The current organization type already exists" +msgstr "" + +#: tickets/serializers/super_ticket.py:11 +msgid "Processor" +msgstr "" + +#: tickets/serializers/ticket/apply_asset.py:20 +msgid "Apply actions" +msgstr "" + +#: tickets/serializers/ticket/common.py:15 +#: tickets/serializers/ticket/common.py:77 +msgid "Created by ticket ({}-{})" +msgstr "" + +#: tickets/serializers/ticket/common.py:67 +msgid "The expiration date should be greater than the start date" +msgstr "" + +#: tickets/serializers/ticket/common.py:84 +msgid "Permission named `{}` already exists" +msgstr "" + +#: tickets/serializers/ticket/ticket.py:96 +msgid "The ticket flow `{}` does not exist" +msgstr "" + +#: tickets/templates/tickets/_msg_ticket.html:20 +msgid "View details" +msgstr "" + +#: tickets/templates/tickets/_msg_ticket.html:25 +msgid "Direct approval" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:11 +msgid "Ticket information" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:29 +#: tickets/views/approve.py:38 +msgid "Ticket approval" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:45 +msgid "Approval" +msgstr "" + +#: tickets/templates/tickets/approve_check_password.html:54 +msgid "Go Login" +msgstr "" + +#: tickets/views/approve.py:39 +msgid "" +"This ticket does not exist, the process has ended, or this link has expired" +msgstr "" + +#: tickets/views/approve.py:68 +msgid "Click the button below to approve or reject" +msgstr "" + +#: tickets/views/approve.py:70 +msgid "After successful authentication, this ticket can be approved directly" +msgstr "" + +#: tickets/views/approve.py:92 +msgid "Illegal approval action" +msgstr "" + +#: tickets/views/approve.py:105 +msgid "This user is not authorized to approve this ticket" +msgstr "" + +#: users/api/user.py:183 +msgid "Could not reset self otp, use profile reset instead" +msgstr "" + +#: users/apps.py:9 +msgid "Users" +msgstr "" + +#: users/const.py:10 +msgid "System administrator" +msgstr "" + +#: users/const.py:11 +msgid "System auditor" +msgstr "" + +#: users/const.py:12 +msgid "Organization administrator" +msgstr "" + +#: users/const.py:13 +msgid "Organization auditor" +msgstr "" + +#: users/const.py:18 +msgid "Reset link will be generated and sent to the user" +msgstr "" + +#: users/const.py:19 +msgid "Set password" +msgstr "" + +#: users/exceptions.py:10 +msgid "MFA not enabled" +msgstr "" + +#: users/exceptions.py:20 +msgid "MFA method not support" +msgstr "" + +#: users/forms/profile.py:50 +msgid "" +"When enabled, you will enter the MFA binding process the next time you log " +"in. you can also directly bind in \"personal information -> quick " +"modification -> change MFA Settings\"!" +msgstr "" + +#: users/forms/profile.py:61 +msgid "* Enable MFA to make the account more secure." +msgstr "" + +#: users/forms/profile.py:70 +msgid "" +"In order to protect you and your company, please keep your account, password " +"and key sensitive information properly. (for example: setting complex " +"password, enabling MFA)" +msgstr "" + +#: users/forms/profile.py:77 +msgid "Finish" +msgstr "" + +#: users/forms/profile.py:84 +msgid "New password" +msgstr "" + +#: users/forms/profile.py:89 +msgid "Confirm password" +msgstr "" + +#: users/forms/profile.py:97 +msgid "Password does not match" +msgstr "" + +#: users/forms/profile.py:118 +msgid "Old password" +msgstr "" + +#: users/forms/profile.py:128 +msgid "Old password error" +msgstr "" + +#: users/forms/profile.py:138 +msgid "Automatically configure and download the SSH key" +msgstr "" + +#: users/forms/profile.py:140 +msgid "ssh public key" +msgstr "" + +#: users/forms/profile.py:141 +msgid "ssh-rsa AAAA..." +msgstr "" + +#: users/forms/profile.py:142 +msgid "Paste your id_rsa.pub here." +msgstr "" + +#: users/forms/profile.py:155 +msgid "Public key should not be the same as your old one." +msgstr "" + +#: users/forms/profile.py:159 users/serializers/profile.py:100 +#: users/serializers/profile.py:183 users/serializers/profile.py:210 +msgid "Not a valid ssh public key" +msgstr "" + +#: users/forms/profile.py:170 users/models/user.py:708 +msgid "Public key" +msgstr "" + +#: users/models/user.py:561 +msgid "Force enable" +msgstr "" + +#: users/models/user.py:631 +msgid "Local" +msgstr "" + +#: users/models/user.py:687 users/serializers/user.py:204 +msgid "Is service account" +msgstr "" + +#: users/models/user.py:689 +msgid "Avatar" +msgstr "" + +#: users/models/user.py:692 +msgid "Wechat" +msgstr "" + +#: users/models/user.py:695 +msgid "Phone" +msgstr "" + +#: users/models/user.py:701 +msgid "OTP secret key" +msgstr "" + +#: users/models/user.py:705 +msgid "Private key" +msgstr "" + +#: users/models/user.py:711 +msgid "Secret key" +msgstr "" + +#: users/models/user.py:716 users/serializers/profile.py:149 +#: users/serializers/user.py:201 +msgid "Is first login" +msgstr "" + +#: users/models/user.py:727 +msgid "Source" +msgstr "" + +#: users/models/user.py:731 +msgid "Date password last updated" +msgstr "" + +#: users/models/user.py:734 +msgid "Need update password" +msgstr "" + +#: users/models/user.py:909 +msgid "Can invite user" +msgstr "" + +#: users/models/user.py:910 +msgid "Can remove user" +msgstr "" + +#: users/models/user.py:911 +msgid "Can match user" +msgstr "" + +#: users/models/user.py:920 +msgid "Administrator" +msgstr "" + +#: users/models/user.py:923 +msgid "Administrator is the super user of system" +msgstr "" + +#: users/models/user.py:948 +msgid "User password history" +msgstr "" + +#: users/notifications.py:55 +#: users/templates/users/_msg_password_expire_reminder.html:17 +#: users/templates/users/reset_password.html:5 +#: users/templates/users/reset_password.html:6 +msgid "Reset password" +msgstr "" + +#: users/notifications.py:85 users/views/profile/reset.py:194 +msgid "Reset password success" +msgstr "" + +#: users/notifications.py:117 +msgid "Reset public key success" +msgstr "" + +#: users/notifications.py:143 +msgid "Password is about expire" +msgstr "" + +#: users/notifications.py:171 +msgid "Account is about expire" +msgstr "" + +#: users/notifications.py:193 +msgid "Reset SSH Key" +msgstr "" + +#: users/notifications.py:214 +msgid "Reset MFA" +msgstr "" + +#: users/serializers/profile.py:30 +msgid "The old password is incorrect" +msgstr "" + +#: users/serializers/profile.py:37 users/serializers/profile.py:197 +msgid "Password does not match security rules" +msgstr "" + +#: users/serializers/profile.py:41 +msgid "The new password cannot be the last {} passwords" +msgstr "" + +#: users/serializers/profile.py:49 users/serializers/profile.py:71 +msgid "The newly set password is inconsistent" +msgstr "" + +#: users/serializers/user.py:30 +msgid "System roles" +msgstr "" + +#: users/serializers/user.py:35 +msgid "Org roles" +msgstr "" + +#: users/serializers/user.py:38 +msgid "System roles display" +msgstr "" + +#: users/serializers/user.py:40 +msgid "Org roles display" +msgstr "" + +#: users/serializers/user.py:90 +#: xpack/plugins/change_auth_plan/models/base.py:35 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 +msgid "Password strategy" +msgstr "" + +#: users/serializers/user.py:92 +msgid "MFA enabled" +msgstr "" + +#: users/serializers/user.py:94 +msgid "MFA force enabled" +msgstr "" + +#: users/serializers/user.py:97 +msgid "MFA level display" +msgstr "" + +#: users/serializers/user.py:99 +msgid "Login blocked" +msgstr "" + +#: users/serializers/user.py:102 +msgid "Can public key authentication" +msgstr "" + +#: users/serializers/user.py:206 +msgid "Avatar url" +msgstr "" + +#: users/serializers/user.py:208 +msgid "Groups name" +msgstr "" + +#: users/serializers/user.py:209 +msgid "Source name" +msgstr "" + +#: users/serializers/user.py:210 +msgid "Organization role name" +msgstr "" + +#: users/serializers/user.py:211 +msgid "Super role name" +msgstr "" + +#: users/serializers/user.py:212 +msgid "Total role name" +msgstr "" + +#: users/serializers/user.py:214 +msgid "Is wecom bound" +msgstr "" + +#: users/serializers/user.py:215 +msgid "Is dingtalk bound" +msgstr "" + +#: users/serializers/user.py:216 +msgid "Is feishu bound" +msgstr "" + +#: users/serializers/user.py:217 +msgid "Is OTP bound" +msgstr "" + +#: users/serializers/user.py:219 +msgid "System role name" +msgstr "" + +#: users/serializers/user.py:325 +msgid "Select users" +msgstr "" + +#: users/serializers/user.py:326 +msgid "For security, only list several users" +msgstr "" + +#: users/serializers/user.py:362 +msgid "name not unique" +msgstr "" + +#: users/templates/users/_msg_account_expire_reminder.html:7 +msgid "Your account will expire in" +msgstr "" + +#: users/templates/users/_msg_account_expire_reminder.html:8 +msgid "" +"In order not to affect your normal work, please contact the administrator " +"for confirmation." +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:7 +msgid "Your password will expire in" +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:8 +msgid "" +"For your account security, please click on the link below to update your " +"password in time" +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:11 +msgid "Click here update password" +msgstr "" + +#: users/templates/users/_msg_password_expire_reminder.html:16 +msgid "If your password has expired, please click the link below to" +msgstr "" + +#: users/templates/users/_msg_reset_mfa.html:7 +msgid "Your MFA has been reset by site administrator" +msgstr "" + +#: users/templates/users/_msg_reset_mfa.html:8 +#: users/templates/users/_msg_reset_ssh_key.html:8 +msgid "Please click the link below to set" +msgstr "" + +#: users/templates/users/_msg_reset_mfa.html:11 +#: users/templates/users/_msg_reset_ssh_key.html:11 +msgid "Click here set" +msgstr "" + +#: users/templates/users/_msg_reset_ssh_key.html:7 +msgid "Your ssh public key has been reset by site administrator" +msgstr "" + +#: users/templates/users/_msg_user_created.html:15 +msgid "click here to set your password" +msgstr "" + +#: users/templates/users/forgot_password.html:32 +msgid "Input your email account, that will send a email to your" +msgstr "" + +#: users/templates/users/forgot_password.html:35 +msgid "" +"Enter your mobile number and a verification code will be sent to your phone" +msgstr "" + +#: users/templates/users/forgot_password.html:57 +msgid "Email account" +msgstr "" + +#: users/templates/users/forgot_password.html:61 +msgid "Mobile number" +msgstr "" + +#: users/templates/users/forgot_password.html:68 +msgid "Send" +msgstr "" + +#: users/templates/users/forgot_password.html:72 +#: users/templates/users/forgot_password_previewing.html:30 +msgid "Submit" +msgstr "" + +#: users/templates/users/forgot_password_previewing.html:21 +msgid "Please enter the username for which you want to retrieve the password" +msgstr "" + +#: users/templates/users/mfa_setting.html:24 +msgid "Enable MFA" +msgstr "" + +#: users/templates/users/mfa_setting.html:30 +msgid "MFA force enable, cannot disable" +msgstr "" + +#: users/templates/users/mfa_setting.html:48 +msgid "MFA setting" +msgstr "" + +#: users/templates/users/reset_password.html:23 +msgid "Your password must satisfy" +msgstr "" + +#: users/templates/users/reset_password.html:24 +msgid "Password strength" +msgstr "" + +#: users/templates/users/reset_password.html:48 +msgid "Very weak" +msgstr "" + +#: users/templates/users/reset_password.html:49 +msgid "Weak" +msgstr "" + +#: users/templates/users/reset_password.html:51 +msgid "Medium" +msgstr "" + +#: users/templates/users/reset_password.html:52 +msgid "Strong" +msgstr "" + +#: users/templates/users/reset_password.html:53 +msgid "Very strong" +msgstr "" + +#: users/templates/users/user_otp_check_password.html:6 +msgid "Enable OTP" +msgstr "" + +#: users/templates/users/user_otp_enable_bind.html:6 +msgid "Bind one-time password authenticator" +msgstr "" + +#: users/templates/users/user_otp_enable_bind.html:13 +msgid "" +"Use the MFA Authenticator application to scan the following qr code for a 6-" +"bit verification code" +msgstr "" + +#: users/templates/users/user_otp_enable_bind.html:22 +#: users/templates/users/user_verify_mfa.html:27 +msgid "Six figures" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:6 +msgid "Install app" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:13 +msgid "" +"Download and install the MFA Authenticator application on your phone or " +"applet of WeChat" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:18 +msgid "Android downloads" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:23 +msgid "iPhone downloads" +msgstr "" + +#: users/templates/users/user_otp_enable_install_app.html:26 +msgid "" +"After installation, click the next step to enter the binding page (if " +"installed, go to the next step directly)." +msgstr "" + +#: users/templates/users/user_password_verify.html:8 +#: users/templates/users/user_password_verify.html:9 +msgid "Verify password" +msgstr "" + +#: users/templates/users/user_verify_mfa.html:9 +msgid "Authenticate" +msgstr "" + +#: users/templates/users/user_verify_mfa.html:15 +msgid "" +"The account protection has been opened, please complete the following " +"operations according to the prompts" +msgstr "" + +#: users/templates/users/user_verify_mfa.html:17 +msgid "Open MFA Authenticator and enter the 6-bit dynamic code" +msgstr "" + +#: users/views/profile/otp.py:87 +msgid "Already bound" +msgstr "" + +#: users/views/profile/otp.py:88 +msgid "MFA already bound, disable first, then bound" +msgstr "" + +#: users/views/profile/otp.py:115 +msgid "OTP enable success" +msgstr "" + +#: users/views/profile/otp.py:116 +msgid "OTP enable success, return login page" +msgstr "" + +#: users/views/profile/otp.py:158 +msgid "Disable OTP" +msgstr "" + +#: users/views/profile/otp.py:164 +msgid "OTP disable success" +msgstr "" + +#: users/views/profile/otp.py:165 +msgid "OTP disable success, return login page" +msgstr "" + +#: users/views/profile/password.py:36 users/views/profile/password.py:41 +msgid "Password invalid" +msgstr "" + +#: users/views/profile/reset.py:47 +msgid "" +"Non-local users can log in only from third-party platforms and cannot change " +"their passwords: {}" +msgstr "" + +#: users/views/profile/reset.py:149 users/views/profile/reset.py:160 +msgid "Token invalid or expired" +msgstr "" + +#: users/views/profile/reset.py:165 +msgid "User auth from {}, go there change password" +msgstr "" + +#: users/views/profile/reset.py:172 +msgid "* Your password does not meet the requirements" +msgstr "" + +#: users/views/profile/reset.py:178 +msgid "* The new password cannot be the last {} passwords" +msgstr "" + +#: users/views/profile/reset.py:195 +msgid "Reset password success, return to login page" +msgstr "" + +#: xpack/apps.py:8 +msgid "XPACK" +msgstr "" + +#: xpack/plugins/change_auth_plan/meta.py:9 +#: xpack/plugins/change_auth_plan/models/asset.py:124 +msgid "Change auth plan" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:45 +#: xpack/plugins/change_auth_plan/models/app.py:94 +msgid "Application change auth plan" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:98 +#: xpack/plugins/change_auth_plan/models/app.py:150 +msgid "Application change auth plan execution" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:143 +msgid "App" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:155 +msgid "Application change auth plan task" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/app.py:179 +#: xpack/plugins/change_auth_plan/models/asset.py:264 +msgid "Password cannot be set to blank, exit. " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:68 +msgid "Asset change auth plan" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:135 +msgid "Asset change auth plan execution" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:211 +msgid "Change auth plan execution" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:218 +msgid "Asset change auth plan task" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:253 +msgid "This asset does not have a privileged user set: " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:259 +msgid "" +"The password and key of the current asset privileged user cannot be changed: " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/asset.py:270 +msgid "Public key cannot be set to null, exit. " +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:114 +msgid "Change auth plan snapshot" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:184 +msgid "Preflight check" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:185 +msgid "Change auth" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:186 +msgid "Verify auth" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:187 +msgid "Keep auth" +msgstr "" + +#: xpack/plugins/change_auth_plan/models/base.py:195 +msgid "Step" +msgstr "" + +#: xpack/plugins/change_auth_plan/serializers/asset.py:30 +msgid "Change Password" +msgstr "" + +#: xpack/plugins/change_auth_plan/serializers/asset.py:31 +msgid "Change SSH Key" +msgstr "" + +#: xpack/plugins/change_auth_plan/serializers/base.py:44 +msgid "Run times" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 +msgid "After many attempts to change the secret, it still failed" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:255 +msgid "Invalid/incorrect password" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:257 +msgid "Failed to connect to the host" +msgstr "" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:259 +msgid "Data could not be sent to remote" +msgstr "" + +#: xpack/plugins/cloud/api.py:40 +msgid "Test connection successful" +msgstr "" + +#: xpack/plugins/cloud/api.py:42 +msgid "Test connection failed: {}" +msgstr "" + +#: xpack/plugins/cloud/const.py:8 +msgid "Alibaba Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:9 +msgid "AWS (International)" +msgstr "" + +#: xpack/plugins/cloud/const.py:10 +msgid "AWS (China)" +msgstr "" + +#: xpack/plugins/cloud/const.py:11 +msgid "Azure (China)" +msgstr "" + +#: xpack/plugins/cloud/const.py:12 +msgid "Azure (International)" +msgstr "" + +#: xpack/plugins/cloud/const.py:14 +msgid "Baidu Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:16 +msgid "KingSoft Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:17 +msgid "Tencent Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:18 +msgid "Tencent Cloud (Lighthouse)" +msgstr "" + +#: xpack/plugins/cloud/const.py:19 +msgid "VMware" +msgstr "" + +#: xpack/plugins/cloud/const.py:20 xpack/plugins/cloud/providers/nutanix.py:13 +msgid "Nutanix" +msgstr "" + +#: xpack/plugins/cloud/const.py:21 +msgid "Huawei Private Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:22 +msgid "Qingyun Private Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:23 +msgid "CTYun Private Cloud" +msgstr "" + +#: xpack/plugins/cloud/const.py:24 +msgid "OpenStack" +msgstr "" + +#: xpack/plugins/cloud/const.py:25 +msgid "Google Cloud Platform" +msgstr "" + +#: xpack/plugins/cloud/const.py:26 +msgid "Fusion Compute" +msgstr "" + +#: xpack/plugins/cloud/const.py:31 +msgid "Private IP" +msgstr "" + +#: xpack/plugins/cloud/const.py:32 +msgid "Public IP" +msgstr "" + +#: xpack/plugins/cloud/const.py:36 +msgid "Instance name" +msgstr "" + +#: xpack/plugins/cloud/const.py:37 +msgid "Instance name and Partial IP" +msgstr "" + +#: xpack/plugins/cloud/const.py:42 +msgid "Succeed" +msgstr "" + +#: xpack/plugins/cloud/const.py:46 +msgid "Unsync" +msgstr "" + +#: xpack/plugins/cloud/const.py:47 +msgid "New Sync" +msgstr "" + +#: xpack/plugins/cloud/const.py:48 +msgid "Synced" +msgstr "" + +#: xpack/plugins/cloud/const.py:49 +msgid "Released" +msgstr "" + +#: xpack/plugins/cloud/meta.py:9 +msgid "Cloud center" +msgstr "" + +#: xpack/plugins/cloud/models.py:32 +msgid "Provider" +msgstr "" + +#: xpack/plugins/cloud/models.py:36 +msgid "Validity" +msgstr "" + +#: xpack/plugins/cloud/models.py:41 +msgid "Cloud account" +msgstr "" + +#: xpack/plugins/cloud/models.py:43 +msgid "Test cloud account" +msgstr "" + +#: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:38 +msgid "Regions" +msgstr "" + +#: xpack/plugins/cloud/models.py:93 +msgid "Hostname strategy" +msgstr "" + +#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:72 +msgid "Unix admin user" +msgstr "" + +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:73 +msgid "Windows admin user" +msgstr "" + +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:46 +msgid "IP network segment group" +msgstr "" + +#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:51 +msgid "Sync IP type" +msgstr "" + +#: xpack/plugins/cloud/models.py:118 xpack/plugins/cloud/serializers/task.py:76 +msgid "Always update" +msgstr "" + +#: xpack/plugins/cloud/models.py:124 +msgid "Date last sync" +msgstr "" + +#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +msgid "Sync instance task" +msgstr "" + +#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +msgid "Date sync" +msgstr "" + +#: xpack/plugins/cloud/models.py:185 +msgid "Sync instance task execution" +msgstr "" + +#: xpack/plugins/cloud/models.py:209 +msgid "Sync task" +msgstr "" + +#: xpack/plugins/cloud/models.py:213 +msgid "Sync instance task history" +msgstr "" + +#: xpack/plugins/cloud/models.py:216 +msgid "Instance" +msgstr "" + +#: xpack/plugins/cloud/models.py:233 +msgid "Sync instance detail" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:17 +msgid "China (Beijing)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:18 +msgid "China (Ningxia)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:21 +msgid "US East (Ohio)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:22 +msgid "US East (N. Virginia)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:23 +msgid "US West (N. California)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:24 +msgid "US West (Oregon)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:25 +msgid "Africa (Cape Town)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:26 +msgid "Asia Pacific (Hong Kong)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:27 +msgid "Asia Pacific (Mumbai)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:28 +msgid "Asia Pacific (Osaka-Local)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:29 +msgid "Asia Pacific (Seoul)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:30 +msgid "Asia Pacific (Singapore)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:31 +msgid "Asia Pacific (Sydney)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:32 +msgid "Asia Pacific (Tokyo)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:33 +msgid "Canada (Central)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:34 +msgid "Europe (Frankfurt)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:35 +msgid "Europe (Ireland)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:36 +msgid "Europe (London)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:37 +msgid "Europe (Milan)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:38 +msgid "Europe (Paris)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:39 +msgid "Europe (Stockholm)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:40 +msgid "Middle East (Bahrain)" +msgstr "" + +#: xpack/plugins/cloud/providers/aws_international.py:41 +msgid "South America (São Paulo)" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 +msgid "CN North-Beijing" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:55 +#: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 +msgid "CN South-Guangzhou" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:56 +msgid "CN East-Suzhou" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:57 +#: xpack/plugins/cloud/providers/huaweicloud.py:48 +msgid "CN-Hong Kong" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:58 +msgid "CN Center-Wuhan" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:59 +msgid "CN North-Baoding" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 +msgid "CN East-Shanghai" +msgstr "" + +#: xpack/plugins/cloud/providers/baiducloud.py:61 +#: xpack/plugins/cloud/providers/huaweicloud.py:47 +msgid "AP-Singapore" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:35 +msgid "AF-Johannesburg" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:36 +msgid "CN North-Beijing4" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:37 +msgid "CN North-Beijing1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:38 +msgid "CN East-Shanghai2" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:39 +msgid "CN East-Shanghai1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:41 +msgid "LA-Mexico City1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:42 +msgid "LA-Santiago" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:43 +msgid "LA-Sao Paulo1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:44 +msgid "EU-Paris" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:45 +msgid "CN Southwest-Guiyang1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:46 +msgid "AP-Bangkok" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:50 +msgid "CN Northeast-Dalian" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:51 +msgid "CN North-Ulanqab1" +msgstr "" + +#: xpack/plugins/cloud/providers/huaweicloud.py:52 +msgid "CN South-Guangzhou-InvitationOnly" +msgstr "" + +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "" + +#: xpack/plugins/cloud/serializers/account.py:65 +msgid "Validity display" +msgstr "" + +#: xpack/plugins/cloud/serializers/account.py:66 +msgid "Provider display" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:35 +msgid "Client ID" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:41 +msgid "Tenant ID" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:44 +msgid "Subscription ID" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:95 +#: xpack/plugins/cloud/serializers/account_attrs.py:100 +#: xpack/plugins/cloud/serializers/account_attrs.py:116 +#: xpack/plugins/cloud/serializers/account_attrs.py:141 +msgid "API Endpoint" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:106 +msgid "Auth url" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:107 +msgid "eg: http://openstack.example.com:5000/v3" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:110 +msgid "User domain" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:117 +msgid "Cert File" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:118 +msgid "Key File" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:134 +msgid "Service account key" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:135 +msgid "The file is in JSON format" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:148 +msgid "IP address invalid `{}`, {}" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:154 +msgid "" +"Format for comma-delimited string,Such as: 192.168.1.0/24, " +"10.0.0.0-10.0.0.255" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:158 +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 " +"synchronized.
If the port is 0, all IP addresses are valid." +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:166 +msgid "Hostname prefix" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:169 +msgid "IP segment" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:173 +msgid "Test port" +msgstr "" + +#: xpack/plugins/cloud/serializers/account_attrs.py:176 +msgid "Test timeout" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:29 +msgid "" +"Only instances matching the IP range will be synced.
If the instance " +"contains multiple IP addresses, the first IP address that matches will be " +"used as the IP for the created asset.
The default value of * means sync " +"all instances and randomly match IP addresses.
Format for comma-" +"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:36 +msgid "History count" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:37 +msgid "Instance count" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:70 +msgid "Linux admin user" +msgstr "" + +#: xpack/plugins/cloud/serializers/task.py:75 +#: xpack/plugins/gathered_user/serializers.py:20 +msgid "Periodic display" +msgstr "" + +#: xpack/plugins/cloud/utils.py:69 +msgid "Account unavailable" +msgstr "" + +#: xpack/plugins/gathered_user/meta.py:11 +msgid "Gathered user" +msgstr "" + +#: xpack/plugins/gathered_user/models.py:34 +msgid "Gather user task" +msgstr "" + +#: xpack/plugins/gathered_user/models.py:80 +msgid "gather user task execution" +msgstr "" + +#: xpack/plugins/gathered_user/models.py:86 +msgid "Assets is empty, please change nodes" +msgstr "" + +#: xpack/plugins/gathered_user/serializers.py:21 +msgid "Executed times" +msgstr "" + +#: xpack/plugins/interface/api.py:52 +msgid "Restore default successfully." +msgstr "" + +#: xpack/plugins/interface/meta.py:10 +msgid "Interface settings" +msgstr "" + +#: xpack/plugins/interface/models.py:22 +msgid "Title of login page" +msgstr "" + +#: xpack/plugins/interface/models.py:26 +msgid "Image of login page" +msgstr "" + +#: xpack/plugins/interface/models.py:30 +msgid "Website icon" +msgstr "" + +#: xpack/plugins/interface/models.py:34 +msgid "Logo of management page" +msgstr "" + +#: xpack/plugins/interface/models.py:38 +msgid "Logo of logout page" +msgstr "" + +#: xpack/plugins/interface/models.py:40 +msgid "Theme" +msgstr "" + +#: xpack/plugins/interface/models.py:43 xpack/plugins/interface/models.py:84 +msgid "Interface setting" +msgstr "" + +#: xpack/plugins/license/api.py:50 +msgid "License import successfully" +msgstr "" + +#: xpack/plugins/license/api.py:51 +msgid "License is invalid" +msgstr "" + +#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:127 +msgid "License" +msgstr "" + +#: xpack/plugins/license/models.py:71 +msgid "Standard edition" +msgstr "" + +#: xpack/plugins/license/models.py:73 +msgid "Enterprise edition" +msgstr "" + +#: xpack/plugins/license/models.py:75 +msgid "Ultimate edition" +msgstr "" + +#: xpack/plugins/license/models.py:77 +msgid "Community edition" +msgstr "" diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index b5097a037..ee518c008 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -1,14 +1,15 @@ -from rest_framework import viewsets -from rest_framework_bulk import BulkModelViewSet +from rest_framework.views import APIView + +from rest_framework.response import Response -from common.mixins import CommonApiMixin from ops.api.base import SelfBulkModelViewSet from ops.models import Job, JobExecution from ops.serializers.job import JobSerializer, JobExecutionSerializer -__all__ = ['JobViewSet', 'JobExecutionViewSet'] +__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView'] from ops.tasks import run_ops_job_execution +from ops.variables import JMS_JOB_VARIABLE_HELP def set_task_to_serializer_data(serializer, task): @@ -64,3 +65,11 @@ class JobExecutionViewSet(SelfBulkModelViewSet): if job_id: query_set = query_set.filter(job_id=job_id) return query_set + + +class JobRunVariableHelpAPIView(APIView): + rbac_perms = () + permission_classes = () + + def get(self, request, **kwargs): + return Response(data=JMS_JOB_VARIABLE_HELP) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 211d386d9..e96801535 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -14,6 +14,7 @@ __all__ = ["Job", "JobExecution"] from common.db.models import JMSBaseModel from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.mixin import PeriodTaskModelMixin +from ops.variables import * class Job(JMSBaseModel, PeriodTaskModelMixin): @@ -128,6 +129,9 @@ class JobExecution(JMSBaseModel): else: extra_vars = {} + static_variables = self.gather_static_variables() + extra_vars.update(static_variables) + if self.job.type == 'adhoc': args = self.compile_shell() runner = AdHocRunner( @@ -142,6 +146,14 @@ class JobExecution(JMSBaseModel): raise Exception("unsupported job type") return runner + def gather_static_variables(self): + default = { + JMS_USERNAME: self.creator.username, + JMS_JOB_ID: self.job.id, + JMS_JOB_NAME: self.job.name, + } + return default + @property def short_id(self): return str(self.id).split('-')[-1] diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 05993dc08..1ac944d42 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -27,6 +27,7 @@ class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin): class JobExecutionSerializer(serializers.ModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) + job_type = serializers.ReadOnlyField(label=_("Job type")) class Meta: model = JobExecution diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index a8b71734f..a0c2754cf 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -23,6 +23,7 @@ router.register(r'tasks', api.CeleryTaskViewSet, 'task') router.register(r'task-executions', api.CeleryTaskExecutionViewSet, 'task-executions') urlpatterns = [ + path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'), path('ansible/job-execution//log/', api.AnsibleTaskLogApi.as_view(), name='job-execution-log'), diff --git a/apps/ops/variables.py b/apps/ops/variables.py new file mode 100644 index 000000000..2d9c078af --- /dev/null +++ b/apps/ops/variables.py @@ -0,0 +1,33 @@ +from django.utils.translation import gettext_lazy as _ + +# JumpServer +JMS_USERNAME = "jms_username" + +# ASSENT +JMS_ASSET_ID = "jms_asset.id" +JMS_ASSET_TYPE = "jms_asset.type" +JMS_ASSET_CATEGORY = "jms_asset.category" +JMS_ASSET_PROTOCOL = "jms_asset.protocol" +JMS_ASSET_PORT = "jms_asset.port" +JMS_ASSET_NAME = "jms_asset.name" +JMS_ASSET_ADDRESS = "jms_asset.address" + +# Account +JMS_ACCOUNT_ID = "jms_account.id" +JMS_ACCOUNT_USERNAME = "jms_account.name" + +# JOB +JMS_JOB_ID = "jms_job_id" +JMS_JOB_NAME = "jms_job_name" + +JMS_JOB_VARIABLE_HELP = { + JMS_USERNAME: _('The current user`s username of JumpServer'), + JMS_ASSET_ID: _('The id of the asset in the JumpServer'), + JMS_ASSET_TYPE: _('The type of the asset in the JumpServer'), + JMS_ASSET_CATEGORY: _('The category of the asset in the JumpServer'), + JMS_ASSET_NAME: _('The name of the asset in the JumpServer'), + JMS_ASSET_ADDRESS: _('Address used to connect this asset in JumpServer'), + JMS_ASSET_PORT: _('Port used to connect this asset in JumpServer'), + JMS_JOB_ID: _('ID of the job'), + JMS_JOB_NAME: _('Name of the job'), +} From bf5da830080aab7341201905f4d633ea34b42ab6 Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 6 Dec 2022 19:53:36 +0800 Subject: [PATCH 004/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=8E=88?= =?UTF-8?q?=E6=9D=83=20API=20Name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/nodes.py | 16 ++++++++-------- apps/perms/urls/user_permission.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index eb0d321d5..6a57675f8 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -16,10 +16,10 @@ from .mixin import SelfOrPKUserMixin, RebuildTreeMixin logger = get_logger(__name__) __all__ = [ - 'UserGrantedNodesApi', - 'UserGrantedNodesAsTreeApi', - 'UserGrantedNodeChildrenApi', - 'UserGrantedNodeChildrenAsTreeApi', + 'UserPermedNodesApi', + 'UserPermedNodesAsTreeApi', + 'UserPermedNodeChildrenApi', + 'UserPermedNodeChildrenAsTreeApi', 'BaseGrantedNodeAsTreeApi', 'UserGrantedNodesMixin', ] @@ -95,7 +95,7 @@ class UserGrantedNodesMixin: # API -class UserGrantedNodeChildrenApi( +class UserPermedNodeChildrenApi( SelfOrPKUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenApi @@ -104,7 +104,7 @@ class UserGrantedNodeChildrenApi( pass -class UserGrantedNodeChildrenAsTreeApi( +class UserPermedNodeChildrenAsTreeApi( SelfOrPKUserMixin, RebuildTreeMixin, UserGrantedNodeChildrenMixin, @@ -114,7 +114,7 @@ class UserGrantedNodeChildrenAsTreeApi( pass -class UserGrantedNodesApi( +class UserPermedNodesApi( SelfOrPKUserMixin, UserGrantedNodesMixin, BaseGrantedNodeApi @@ -123,7 +123,7 @@ class UserGrantedNodesApi( pass -class UserGrantedNodesAsTreeApi( +class UserPermedNodesAsTreeApi( SelfOrPKUserMixin, RebuildTreeMixin, UserGrantedNodesMixin, diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index c3a555375..1ff44eeee 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -14,13 +14,13 @@ user_permission_urlpatterns = [ name='user-ungroup-assets-as-tree'), # nodes - path('/nodes/', api.UserGrantedNodesApi.as_view(), + path('/nodes/', api.UserPermedNodesApi.as_view(), name='user-nodes'), - path('/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), + path('/nodes/tree/', api.UserPermedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'), - path('/nodes/children/', api.UserGrantedNodeChildrenApi.as_view(), + path('/nodes/children/', api.UserPermedNodeChildrenApi.as_view(), name='user-nodes-children'), - path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), + path('/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'), # node-assets From e7e3b603a96e2b6ff3a8fb7ebde4551a03534743 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 7 Dec 2022 10:13:08 +0800 Subject: [PATCH 005/132] =?UTF-8?q?perf:=20=E6=B8=85=E7=90=86=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E4=BE=9D=E8=B5=96=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 745a23286..5baa5b953 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -39,7 +39,6 @@ jms-storage==0.0.44 simplejson==3.17.6 six==1.16.0 sshpubkeys==3.3.1 -sshtunnel==0.4.0 uritemplate==4.1.1 urllib3==1.26.9 vine==5.0.0 From dbee3ed30d96d3012727421eeb1ad4c4d136bc72 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Dec 2022 15:09:01 +0800 Subject: [PATCH 006/132] =?UTF-8?q?feat:=20connect=20token=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20Rdp=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../signal_handlers/node_assets_mapping.py | 6 +- apps/authentication/api/connection_token.py | 72 +++-- .../authentication/models/connection_token.py | 78 +++++- .../serializers/connect_token_secret.py | 54 ++-- .../serializers/connection_token.py | 1 - apps/jumpserver/settings/base.py | 11 +- apps/notifications/signal_handlers.py | 27 +- apps/orgs/signal_handlers/common.py | 23 +- apps/settings/signal_handlers.py | 6 +- apps/terminal/api/component/__init__.py | 7 +- .../terminal/api/component/connect_methods.py | 25 ++ apps/terminal/api/component/terminal.py | 17 +- apps/terminal/connect_methods.py | 255 ++++++++++++++++++ apps/terminal/const.py | 229 ---------------- apps/terminal/models/applet/applet.py | 42 ++- apps/terminal/signal_handlers.py | 30 ++- 16 files changed, 535 insertions(+), 348 deletions(-) create mode 100644 apps/terminal/api/component/connect_methods.py create mode 100644 apps/terminal/connect_methods.py diff --git a/apps/assets/signal_handlers/node_assets_mapping.py b/apps/assets/signal_handlers/node_assets_mapping.py index b242f3be8..27640fc76 100644 --- a/apps/assets/signal_handlers/node_assets_mapping.py +++ b/apps/assets/signal_handlers/node_assets_mapping.py @@ -20,13 +20,9 @@ logger = get_logger(__file__) # ------------------------------------ -def get_node_assets_mapping_for_memory_pub_sub(): - return RedisPubSub('fm.node_all_asset_ids_memory_mapping') - - class NodeAssetsMappingForMemoryPubSub(LazyObject): def _setup(self): - self._wrapped = get_node_assets_mapping_for_memory_pub_sub() + self._wrapped = RedisPubSub('fm.node_all_asset_ids_memory_mapping') node_assets_mapping_for_memory_pub_sub = NodeAssetsMappingForMemoryPubSub() diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 97ef3b5ff..60528066e 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -19,12 +19,12 @@ from common.utils import random_string from common.utils.django import get_request_os from orgs.mixins.api import RootOrgViewMixin from perms.models import ActionChoices -from terminal.const import NativeClient, TerminalType +from terminal.connect_methods import NativeClient, ConnectMethodUtil from terminal.models import EndpointRule, Applet from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, - SuperConnectionTokenSerializer, + SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer ) __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] @@ -115,7 +115,8 @@ class RDPFileClientProtocolURLMixin: rdp_options['audiomode:i'] = self.parse_env_bool('JUMPSERVER_DISABLE_AUDIO', 'false', '2', '0') # 设置远程应用 - self.set_applet_info(token, rdp_options) + remote_app_options = token.get_remote_app_option() + rdp_options.update(remote_app_options) # 文件名 name = token.asset.name @@ -145,7 +146,7 @@ class RDPFileClientProtocolURLMixin: _os = get_request_os(self.request) connect_method_name = token.connect_method - connect_method_dict = TerminalType.get_connect_method( + connect_method_dict = ConnectMethodUtil.get_connect_method( token.connect_method, token.protocol, _os ) if connect_method_dict is None: @@ -227,38 +228,16 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView search_fields = filterset_fields serializer_classes = { 'default': ConnectionTokenSerializer, - 'get_secret_detail': ConnectionTokenSecretSerializer, } rbac_perms = { 'list': 'authentication.view_connectiontoken', 'retrieve': 'authentication.view_connectiontoken', 'create': 'authentication.add_connectiontoken', 'expire': 'authentication.add_connectiontoken', - 'get_secret_detail': 'authentication.view_connectiontokensecret', 'get_rdp_file': 'authentication.add_connectiontoken', 'get_client_protocol_url': 'authentication.add_connectiontoken', } - @action(methods=['POST'], detail=False, url_path='secret') - def get_secret_detail(self, request, *args, **kwargs): - """ 非常重要的 api, 在逻辑层再判断一下 rbac 权限, 双重保险 """ - rbac_perm = 'authentication.view_connectiontokensecret' - if not request.user.has_perm(rbac_perm): - raise PermissionDenied('Not allow to view secret') - - token_id = request.data.get('id') or '' - token = get_object_or_404(ConnectionToken, pk=token_id) - if token.is_expired: - raise ValidationError({'id': 'Token is expired'}) - - token.is_valid() - serializer = self.get_serializer(instance=token) - expire_now = request.data.get('expire_now', True) - if expire_now: - token.expire() - - return Response(serializer.data, status=status.HTTP_200_OK) - def get_queryset(self): queryset = ConnectionToken.objects \ .filter(user=self.request.user) \ @@ -305,10 +284,14 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView class SuperConnectionTokenViewSet(ConnectionTokenViewSet): serializer_classes = { 'default': SuperConnectionTokenSerializer, + 'get_secret_detail': ConnectionTokenSecretSerializer, } rbac_perms = { 'create': 'authentication.add_superconnectiontoken', - 'renewal': 'authentication.add_superconnectiontoken' + 'renewal': 'authentication.add_superconnectiontoken', + 'get_secret_detail': 'authentication.view_connectiontokensecret', + 'get_applet_info': 'authentication.view_superconnectiontoken', + 'release_applet_account': 'authentication.view_superconnectiontoken', } def get_queryset(self): @@ -332,3 +315,38 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet): 'msg': f'Token is renewed, date expired: {date_expired}' } return Response(data=data, status=status.HTTP_200_OK) + + @action(methods=['POST'], detail=False, url_path='secret') + def get_secret_detail(self, request, *args, **kwargs): + """ 非常重要的 api, 在逻辑层再判断一下 rbac 权限, 双重保险 """ + rbac_perm = 'authentication.view_connectiontokensecret' + if not request.user.has_perm(rbac_perm): + raise PermissionDenied('Not allow to view secret') + + token_id = request.data.get('id') or '' + token = get_object_or_404(ConnectionToken, pk=token_id) + if token.is_expired: + raise ValidationError({'id': 'Token is expired'}) + + token.is_valid() + serializer = self.get_serializer(instance=token) + expire_now = request.data.get('expire_now', True) + if expire_now: + token.expire() + return Response(serializer.data, status=status.HTTP_200_OK) + + @action(methods=['POST'], detail=False, url_path='applet-option') + def get_applet_info(self, *args, **kwargs): + token_id = self.request.data.get('id') + token = get_object_or_404(ConnectionToken, pk=token_id) + if token.is_expired: + return Response({'error': 'Token expired'}, status=status.HTTP_400_BAD_REQUEST) + data = token.get_applet_option() + serializer = ConnectTokenAppletOptionSerializer(data) + return Response(serializer.data) + + @action(methods=['DELETE', 'POST'], detail=False, url_path='applet-account/release') + def release_applet_account(self, *args, **kwargs): + account_id = self.request.data.get('id') + msg = ConnectionToken.release_applet_account(account_id) + return Response({'msg': msg}) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 4a23d18b8..421ec0969 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -1,6 +1,9 @@ +import base64 +import json from datetime import timedelta from django.conf import settings +from django.core.cache import cache from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -9,9 +12,10 @@ from rest_framework.exceptions import PermissionDenied from assets.const import Protocol from common.db.fields import EncryptCharField from common.db.models import JMSBaseModel -from common.utils import lazyproperty, pretty_string +from common.utils import lazyproperty, pretty_string, bulk_get from common.utils.timezone import as_current_tz from orgs.mixins.models import OrgModelMixin +from terminal.models import Applet def date_expired_default(): @@ -101,6 +105,9 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): error = _('No account') raise PermissionDenied(error) + if timezone.now() - self.date_created < timedelta(seconds=60): + return True, None + if not self.permed_account or not self.permed_account.actions: msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( self.user, self.asset, self.account @@ -115,6 +122,75 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def platform(self): return self.asset.platform + @lazyproperty + def connect_method_object(self): + from common.utils import get_request_os + from jumpserver.utils import get_current_request + from terminal.connect_methods import ConnectMethodUtil + + request = get_current_request() + os = get_request_os(request) if request else 'windows' + method = ConnectMethodUtil.get_connect_method( + self.connect_method, protocol=self.protocol, os=os + ) + return method + + def get_remote_app_option(self): + cmdline = { + 'app_name': self.connect_method, + 'user_id': str(self.user.id), + 'asset_id': str(self.asset.id), + 'token_id': str(self.id) + } + cmdline_b64 = base64.b64encode(json.dumps(cmdline).encode()).decode() + app = '||tinker' + options = { + 'remoteapplicationmode:i': '1', + 'remoteapplicationprogram:s': app, + 'remoteapplicationname:s': app, + 'alternate shell:s': app, + 'remoteapplicationcmdline:s': cmdline_b64, + } + return options + + def get_applet_option(self): + method = self.connect_method_object + if not method or method.get('type') != 'applet' or method.get('disabled', False): + return None + + applet = Applet.objects.filter(name=method.get('value')).first() + if not applet: + return None + + host_account = applet.select_host_account() + if not host_account: + return None + + host, account, lock_key, ttl = bulk_get(host_account, ('host', 'account', 'lock_key', 'ttl')) + gateway = host.gateway.select_gateway() if host.domain else None + + data = { + 'id': account.id, + 'applet': applet, + 'host': host, + 'gateway': gateway, + 'account': account, + 'remote_app_option': self.get_remote_app_option() + } + token_account_relate_key = f'token_account_relate_{account.id}' + cache.set(token_account_relate_key, lock_key, ttl) + return data + + @staticmethod + def release_applet_account(account_id): + token_account_relate_key = f'token_account_relate_{account_id}' + lock_key = cache.get(token_account_relate_key) + if lock_key: + cache.delete(lock_key) + cache.delete(token_account_relate_key) + return 'released' + return 'not found or expired' + @lazyproperty def account_object(self): from assets.models import Account diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index f24f3e9c6..e4fd20be0 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -1,19 +1,17 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.drf.fields import ObjectRelatedField from acls.models import CommandGroup, CommandFilterACL from assets.models import Asset, Account, Platform, Gateway, Domain from assets.serializers import PlatformSerializer, AssetProtocolsSerializer -from users.models import User -from perms.serializers.permission import ActionChoicesField +from common.drf.fields import ObjectRelatedField from orgs.mixins.serializers import OrgResourceModelSerializerMixin - +from perms.serializers.permission import ActionChoicesField +from users.models import User from ..models import ConnectionToken - __all__ = [ - 'ConnectionTokenSecretSerializer', + 'ConnectionTokenSecretSerializer', 'ConnectTokenAppletOptionSerializer' ] @@ -96,6 +94,24 @@ class _ConnectionTokenPlatformSerializer(PlatformSerializer): return names +class _ConnectionTokenConnectMethodSerializer(serializers.Serializer): + name = serializers.CharField(label=_('Name')) + protocol = serializers.CharField(label=_('Protocol')) + os = serializers.CharField(label=_('OS')) + is_builtin = serializers.BooleanField(label=_('Is builtin')) + is_active = serializers.BooleanField(label=_('Is active')) + platform = _ConnectionTokenPlatformSerializer(label=_('Platform')) + action = ActionChoicesField(label=_('Action')) + options = serializers.JSONField(label=_('Options')) + + +class _ConnectTokenConnectMethodSerializer(serializers.Serializer): + label = serializers.CharField(label=_('Label')) + value = serializers.CharField(label=_('Value')) + type = serializers.CharField(label=_('Type')) + component = serializers.CharField(label=_('Component')) + + class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = _ConnectionTokenUserSerializer(read_only=True) asset = _ConnectionTokenAssetSerializer(read_only=True) @@ -104,30 +120,28 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): platform = _ConnectionTokenPlatformSerializer(read_only=True) domain = ObjectRelatedField(queryset=Domain.objects, required=False, label=_('Domain')) command_filter_acls = _ConnectionTokenCommandFilterACLSerializer(read_only=True, many=True) + expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) + connect_method = _ConnectTokenConnectMethodSerializer(read_only=True, source='connect_method_object') actions = ActionChoicesField() expire_at = serializers.IntegerField() - expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) - connect_method = serializers.SerializerMethodField(label=_('Connect method')) class Meta: model = ConnectionToken fields = [ 'id', 'value', 'user', 'asset', 'account', 'platform', 'command_filter_acls', 'protocol', - 'domain', 'gateway', 'actions', 'expire_at', 'expire_now', - 'connect_method' + 'domain', 'gateway', 'actions', 'expire_at', + 'expire_now', 'connect_method', ] extra_kwargs = { 'value': {'read_only': True}, } - def get_connect_method(self, obj): - from terminal.const import TerminalType - from common.utils import get_request_os - request = self.context.get('request') - if request: - os = get_request_os(request) - else: - os = 'windows' - method = TerminalType.get_connect_method(obj.connect_method, protocol=obj.protocol, os=os) - return method + +class ConnectTokenAppletOptionSerializer(serializers.Serializer): + id = serializers.CharField(label=_('ID')) + applet = ObjectRelatedField(read_only=True) + host = _ConnectionTokenAssetSerializer(read_only=True) + account = _ConnectionTokenAccountSerializer(read_only=True) + gateway = _ConnectionTokenGatewaySerializer(read_only=True) + remote_app_option = serializers.JSONField(read_only=True) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 2b5b156e8..e45037853 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -2,7 +2,6 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from orgs.mixins.serializers import OrgResourceModelSerializerMixin - from ..models import ConnectionToken __all__ = [ diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index aaedd2ddc..3b19bb16e 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -3,7 +3,6 @@ import platform from redis.sentinel import SentinelManagedSSLConnection - if platform.system() == 'Darwin' and platform.machine() == 'arm64': import pymysql @@ -308,17 +307,22 @@ else: REDIS_SENTINEL_SOCKET_TIMEOUT = None # Cache config + REDIS_OPTIONS = { "REDIS_CLIENT_KWARGS": { "health_check_interval": 30 }, "CONNECTION_POOL_KWARGS": { + 'max_connections': 100, + } +} +if REDIS_USE_SSL: + REDIS_OPTIONS['CONNECTION_POOL_KWARGS'].update({ 'ssl_cert_reqs': REDIS_SSL_REQUIRED, "ssl_keyfile": REDIS_SSL_KEY, "ssl_certfile": REDIS_SSL_CERT, "ssl_ca_certs": REDIS_SSL_CA - } if REDIS_USE_SSL else {} -} + }) if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS: REDIS_LOCATION_NO_DB = "%(protocol)s://%(service_name)s/{}" % { @@ -348,7 +352,6 @@ else: 'host': CONFIG.REDIS_HOST, 'port': CONFIG.REDIS_PORT, } - REDIS_CACHE_DEFAULT = { 'BACKEND': 'redis_lock.django_cache.RedisCache', 'LOCATION': REDIS_LOCATION_NO_DB.format(CONFIG.REDIS_DB_CACHE), diff --git a/apps/notifications/signal_handlers.py b/apps/notifications/signal_handlers.py index aaf3480bf..c0b1f1c1c 100644 --- a/apps/notifications/signal_handlers.py +++ b/apps/notifications/signal_handlers.py @@ -1,32 +1,26 @@ -import json -from importlib import import_module import inspect +from importlib import import_module -from django.utils.functional import LazyObject -from django.db.models.signals import post_save -from django.db.models.signals import post_migrate -from django.dispatch import receiver from django.apps import AppConfig +from django.db.models.signals import post_migrate +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.utils.functional import LazyObject +from common.decorator import on_transaction_commit +from common.utils import get_logger +from common.utils.connection import RedisPubSub from notifications.backends import BACKEND from users.models import User -from common.utils.connection import RedisPubSub -from common.utils import get_logger -from common.decorator import on_transaction_commit from .models import SiteMessage, SystemMsgSubscription, UserMsgSubscription from .notifications import SystemMessage - logger = get_logger(__name__) -def new_site_msg_pub_sub(): - return RedisPubSub('notifications.SiteMessageCome') - - class NewSiteMsgSubPub(LazyObject): def _setup(self): - self._wrapped = new_site_msg_pub_sub() + self._wrapped = RedisPubSub('notifications.SiteMessageCome') new_site_msg_chan = NewSiteMsgSubPub() @@ -78,7 +72,8 @@ def create_system_messages(app_config: AppConfig, **kwargs): sub, created = SystemMsgSubscription.objects.get_or_create(message_type=message_type) if created: obj.post_insert_to_db(sub) - logger.info(f'Create SystemMsgSubscription: package={app_config.module.__package__} type={message_type}') + logger.info( + f'Create SystemMsgSubscription: package={app_config.module.__package__} type={message_type}') except ModuleNotFoundError: pass diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index b32cba2cd..4935eeec9 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -3,35 +3,30 @@ from collections import defaultdict from functools import partial -import django.db.utils -from django.dispatch import receiver from django.conf import settings -from django.db.utils import ProgrammingError, OperationalError -from django.utils.functional import LazyObject from django.db.models.signals import post_save, pre_delete, m2m_changed +from django.db.utils import ProgrammingError, OperationalError +from django.dispatch import receiver +from django.utils.functional import LazyObject -from orgs.utils import tmp_to_org, set_to_default_org -from orgs.models import Organization -from orgs.hands import set_current_org, Node, get_current_org -from perms.models import AssetPermission -from users.models import UserGroup, User from common.const.signals import PRE_REMOVE, POST_REMOVE from common.decorator import on_transaction_commit from common.signals import django_ready from common.utils import get_logger from common.utils.connection import RedisPubSub +from orgs.hands import set_current_org, Node, get_current_org +from orgs.models import Organization +from orgs.utils import tmp_to_org, set_to_default_org +from perms.models import AssetPermission +from users.models import UserGroup, User from users.signals import post_user_leave_org logger = get_logger(__file__) -def get_orgs_mapping_for_memory_pub_sub(): - return RedisPubSub('fm.orgs_mapping') - - class OrgsMappingForMemoryPubSub(LazyObject): def _setup(self): - self._wrapped = get_orgs_mapping_for_memory_pub_sub() + self._wrapped = RedisPubSub('fm.orgs_mapping') orgs_mapping_for_memory_pub_sub = OrgsMappingForMemoryPubSub() diff --git a/apps/settings/signal_handlers.py b/apps/settings/signal_handlers.py index c963488f1..b04ae4f5e 100644 --- a/apps/settings/signal_handlers.py +++ b/apps/settings/signal_handlers.py @@ -18,13 +18,9 @@ from .models import Setting logger = get_logger(__file__) -def get_settings_pub_sub(): - return RedisPubSub('settings') - - class SettingSubPub(LazyObject): def _setup(self): - self._wrapped = get_settings_pub_sub() + self._wrapped = RedisPubSub('settings') setting_pub_sub = SettingSubPub() diff --git a/apps/terminal/api/component/__init__.py b/apps/terminal/api/component/__init__.py index afefe0c18..56432ca42 100644 --- a/apps/terminal/api/component/__init__.py +++ b/apps/terminal/api/component/__init__.py @@ -1,4 +1,5 @@ -from .terminal import * -from .storage import * -from .status import * +from .connect_methods import * from .endpoint import * +from .status import * +from .storage import * +from .terminal import * diff --git a/apps/terminal/api/component/connect_methods.py b/apps/terminal/api/component/connect_methods.py new file mode 100644 index 000000000..a284159d3 --- /dev/null +++ b/apps/terminal/api/component/connect_methods.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import generics +from rest_framework.views import Response + +from common.permissions import IsValidUser +from common.utils import get_request_os +from terminal import serializers +from terminal.connect_methods import ConnectMethodUtil + +__all__ = ['ConnectMethodListApi'] + + +class ConnectMethodListApi(generics.ListAPIView): + serializer_class = serializers.ConnectMethodSerializer + permission_classes = [IsValidUser] + + def get_queryset(self): + os = get_request_os(self.request) + return ConnectMethodUtil.get_protocols_connect_methods(os) + + def list(self, request, *args, **kwargs): + queryset = self.get_queryset() + return Response(queryset) diff --git a/apps/terminal/api/component/terminal.py b/apps/terminal/api/component/terminal.py index df14296f5..d32adf02b 100644 --- a/apps/terminal/api/component/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -10,16 +10,13 @@ from rest_framework.views import APIView, Response from common.drf.api import JMSBulkModelViewSet from common.exceptions import JMSException -from common.permissions import IsValidUser from common.permissions import WithBootstrapToken -from common.utils import get_request_os from terminal import serializers -from terminal.const import TerminalType from terminal.models import Terminal __all__ = [ 'TerminalViewSet', 'TerminalConfig', - 'TerminalRegistrationApi', 'ConnectMethodListApi' + 'TerminalRegistrationApi', ] logger = logging.getLogger(__file__) @@ -72,15 +69,3 @@ class TerminalRegistrationApi(generics.CreateAPIView): return Response(data=data, status=status.HTTP_400_BAD_REQUEST) return super().create(request, *args, **kwargs) - -class ConnectMethodListApi(generics.ListAPIView): - serializer_class = serializers.ConnectMethodSerializer - permission_classes = [IsValidUser] - - def get_queryset(self): - os = get_request_os(self.request) - return TerminalType.get_protocols_connect_methods(os) - - def list(self, request, *args, **kwargs): - queryset = self.get_queryset() - return Response(queryset) diff --git a/apps/terminal/connect_methods.py b/apps/terminal/connect_methods.py new file mode 100644 index 000000000..ef0b2e943 --- /dev/null +++ b/apps/terminal/connect_methods.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# +from collections import defaultdict + +from django.db.models import TextChoices +from django.utils.translation import ugettext_lazy as _ + +from assets.const import Protocol +from .const import TerminalType + + +class WebMethod(TextChoices): + web_gui = 'web_gui', 'Web GUI' + web_cli = 'web_cli', 'Web CLI' + web_sftp = 'web_sftp', 'Web SFTP' + + @classmethod + def get_methods(cls): + return { + Protocol.ssh: [cls.web_cli, cls.web_sftp], + Protocol.telnet: [cls.web_cli], + Protocol.rdp: [cls.web_gui], + Protocol.vnc: [cls.web_gui], + + Protocol.mysql: [cls.web_cli, cls.web_gui], + Protocol.mariadb: [cls.web_cli, cls.web_gui], + Protocol.oracle: [cls.web_cli, cls.web_gui], + Protocol.postgresql: [cls.web_cli, cls.web_gui], + Protocol.sqlserver: [cls.web_cli, cls.web_gui], + Protocol.redis: [cls.web_cli], + Protocol.mongodb: [cls.web_cli], + + Protocol.k8s: [cls.web_gui], + Protocol.http: [] + } + + +class NativeClient(TextChoices): + # Koko + ssh = 'ssh', 'SSH' + putty = 'putty', 'PuTTY' + xshell = 'xshell', 'Xshell' + + # Magnus + mysql = 'db_client_mysql', _('DB Client') + psql = 'db_client_psql', _('DB Client') + sqlplus = 'db_client_sqlplus', _('DB Client') + redis = 'db_client_redis', _('DB Client') + mongodb = 'db_client_mongodb', _('DB Client') + + # Razor + mstsc = 'mstsc', 'Remote Desktop' + + @classmethod + def get_native_clients(cls): + # native client 关注的是 endpoint 的 protocol, + # 比如 telnet mysql, koko 都支持,到那时暴露的是 ssh 协议 + clients = { + Protocol.ssh: { + 'default': [cls.ssh], + 'windows': [cls.putty], + }, + Protocol.rdp: [cls.mstsc], + Protocol.mysql: [cls.mysql], + Protocol.oracle: [cls.sqlplus], + Protocol.postgresql: [cls.psql], + Protocol.redis: [cls.redis], + Protocol.mongodb: [cls.mongodb], + } + return clients + + @classmethod + def get_target_protocol(cls, name, os): + for protocol, clients in cls.get_native_clients().items(): + if isinstance(clients, dict): + clients = clients.get(os) or clients.get('default') + if name in clients: + return protocol + return None + + @classmethod + def get_methods(cls, os='windows'): + clients_map = cls.get_native_clients() + methods = defaultdict(list) + + for protocol, _clients in clients_map.items(): + if isinstance(_clients, dict): + _clients = _clients.get(os, _clients['default']) + for client in _clients: + methods[protocol].append({ + 'value': client.value, + 'label': client.label, + 'type': 'native', + }) + return methods + + @classmethod + def get_launch_command(cls, name, token, endpoint, os='windows'): + username = f'JMS-{token.id}' + commands = { + cls.ssh: f'ssh {username}@{endpoint.host} -p {endpoint.ssh_port}', + cls.putty: f'putty.exe -ssh {username}@{endpoint.host} -P {endpoint.ssh_port}', + cls.xshell: f'xshell.exe -url ssh://{username}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', + # cls.mysql: 'mysql -h {hostname} -P {port} -u {username} -p', + # cls.psql: { + # 'default': 'psql -h {hostname} -p {port} -U {username} -W', + # 'windows': 'psql /h {hostname} /p {port} /U {username} -W', + # }, + # cls.sqlplus: 'sqlplus {username}/{password}@{hostname}:{port}', + # cls.redis: 'redis-cli -h {hostname} -p {port} -a {password}', + } + command = commands.get(name) + if isinstance(command, dict): + command = command.get(os, command.get('default')) + return command + + +class AppletMethod: + @classmethod + def get_methods(cls): + from .models import Applet, AppletHost + + methods = defaultdict(list) + has_applet_hosts = AppletHost.objects.all().exists() + + applets = Applet.objects.filter(is_active=True) + for applet in applets: + for protocol in applet.protocols: + methods[protocol].append({ + 'value': applet.name, + 'label': applet.display_name, + 'type': 'applet', + 'icon': applet.icon, + 'disabled': not applet.is_active or not has_applet_hosts, + }) + return methods + + +class ConnectMethodUtil: + _all_methods = None + + @classmethod + def protocols(cls): + protocols = { + TerminalType.koko: { + 'web_methods': [WebMethod.web_cli, WebMethod.web_sftp], + 'listen': [Protocol.ssh, Protocol.http], + 'support': [ + Protocol.ssh, Protocol.telnet, + Protocol.mysql, Protocol.postgresql, + Protocol.oracle, Protocol.sqlserver, + Protocol.mariadb, Protocol.redis, + Protocol.mongodb, Protocol.k8s, + ], + 'match': 'm2m' + }, + TerminalType.omnidb: { + 'web_methods': [WebMethod.web_gui], + 'listen': [Protocol.http], + 'support': [ + Protocol.mysql, Protocol.postgresql, Protocol.oracle, + Protocol.sqlserver, Protocol.mariadb + ], + 'match': 'm2m' + }, + TerminalType.lion: { + 'web_methods': [WebMethod.web_gui], + 'listen': [Protocol.http], + 'support': [Protocol.rdp, Protocol.vnc], + 'match': 'm2m' + }, + TerminalType.magnus: { + 'listen': [], + 'support': [ + Protocol.mysql, Protocol.postgresql, + Protocol.oracle, Protocol.mariadb + ], + 'match': 'map' + }, + TerminalType.razor: { + 'listen': [Protocol.rdp], + 'support': [Protocol.rdp], + 'match': 'map' + }, + } + return protocols + + @classmethod + def get_connect_method(cls, name, protocol, os='linux'): + methods = cls.get_protocols_connect_methods(os) + protocol_methods = methods.get(protocol, []) + for method in protocol_methods: + if method['value'] == name: + return method + return None + + @classmethod + def refresh_methods(cls): + cls._all_methods = None + + @classmethod + def get_protocols_connect_methods(cls, os): + if cls._all_methods is not None: + return cls._all_methods + + methods = defaultdict(list) + web_methods = WebMethod.get_methods() + native_methods = NativeClient.get_methods(os) + applet_methods = AppletMethod.get_methods() + + for component, component_protocol in cls.protocols().items(): + support = component_protocol['support'] + + for protocol in support: + # Web 方式 + protocol_web_methods = set(web_methods.get(protocol, [])) \ + & set(component_protocol.get('web_methods', [])) + methods[protocol.value].extend([ + { + 'component': component.value, + 'type': 'web', + 'endpoint_protocol': 'http', + 'value': method.value, + 'label': method.label, + } + for method in protocol_web_methods + ]) + + # 客户端方式 + if component_protocol['match'] == 'map': + listen = [protocol] + else: + listen = component_protocol['listen'] + + for listen_protocol in listen: + # Native method + methods[protocol.value].extend([ + { + 'component': component.value, + 'type': 'native', + 'endpoint_protocol': listen_protocol, + **method + } + for method in native_methods[listen_protocol] + ]) + + # 远程应用方式,这个只有 tinker 提供 + for protocol, applet_methods in applet_methods.items(): + for method in applet_methods: + method['listen'] = 'rdp' + method['component'] = TerminalType.tinker.value + methods[protocol].extend(applet_methods) + + cls._all_methods = methods + return methods diff --git a/apps/terminal/const.py b/apps/terminal/const.py index c5ceb3c94..9fcd4cb72 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -1,12 +1,9 @@ # -*- coding: utf-8 -*- # -from collections import defaultdict from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ -from assets.const import Protocol - # Replay & Command Storage Choices # -------------------------------- @@ -44,128 +41,6 @@ class ComponentLoad(TextChoices): return set(dict(cls.choices).keys()) -class WebMethod(TextChoices): - web_gui = 'web_gui', 'Web GUI' - web_cli = 'web_cli', 'Web CLI' - web_sftp = 'web_sftp', 'Web SFTP' - - @classmethod - def get_methods(cls): - return { - Protocol.ssh: [cls.web_cli, cls.web_sftp], - Protocol.telnet: [cls.web_cli], - Protocol.rdp: [cls.web_gui], - Protocol.vnc: [cls.web_gui], - - Protocol.mysql: [cls.web_cli, cls.web_gui], - Protocol.mariadb: [cls.web_cli, cls.web_gui], - Protocol.oracle: [cls.web_cli, cls.web_gui], - Protocol.postgresql: [cls.web_cli, cls.web_gui], - Protocol.sqlserver: [cls.web_cli, cls.web_gui], - Protocol.redis: [cls.web_cli], - Protocol.mongodb: [cls.web_cli], - - Protocol.k8s: [cls.web_gui], - Protocol.http: [] - } - - -class NativeClient(TextChoices): - # Koko - ssh = 'ssh', 'SSH' - putty = 'putty', 'PuTTY' - xshell = 'xshell', 'Xshell' - - # Magnus - mysql = 'db_client_mysql', _('DB Client') - psql = 'db_client_psql', _('DB Client') - sqlplus = 'db_client_sqlplus', _('DB Client') - redis = 'db_client_redis', _('DB Client') - mongodb = 'db_client_mongodb', _('DB Client') - - # Razor - mstsc = 'mstsc', 'Remote Desktop' - - @classmethod - def get_native_clients(cls): - # native client 关注的是 endpoint 的 protocol, - # 比如 telnet mysql, koko 都支持,到那时暴露的是 ssh 协议 - clients = { - Protocol.ssh: { - 'default': [cls.ssh], - 'windows': [cls.putty], - }, - Protocol.rdp: [cls.mstsc], - Protocol.mysql: [cls.mysql], - Protocol.oracle: [cls.sqlplus], - Protocol.postgresql: [cls.psql], - Protocol.redis: [cls.redis], - Protocol.mongodb: [cls.mongodb], - } - return clients - - @classmethod - def get_target_protocol(cls, name, os): - for protocol, clients in cls.get_native_clients().items(): - if isinstance(clients, dict): - clients = clients.get(os) or clients.get('default') - if name in clients: - return protocol - return None - - @classmethod - def get_methods(cls, os='windows'): - clients_map = cls.get_native_clients() - methods = defaultdict(list) - - for protocol, _clients in clients_map.items(): - if isinstance(_clients, dict): - _clients = _clients.get(os, _clients['default']) - for client in _clients: - methods[protocol].append({ - 'value': client.value, - 'label': client.label, - 'type': 'native', - }) - return methods - - @classmethod - def get_launch_command(cls, name, token, endpoint, os='windows'): - username = f'JMS-{token.id}' - commands = { - cls.ssh: f'ssh {username}@{endpoint.host} -p {endpoint.ssh_port}', - cls.putty: f'putty.exe -ssh {username}@{endpoint.host} -P {endpoint.ssh_port}', - cls.xshell: f'xshell.exe -url ssh://{username}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', - # cls.mysql: 'mysql -h {hostname} -P {port} -u {username} -p', - # cls.psql: { - # 'default': 'psql -h {hostname} -p {port} -U {username} -W', - # 'windows': 'psql /h {hostname} /p {port} /U {username} -W', - # }, - # cls.sqlplus: 'sqlplus {username}/{password}@{hostname}:{port}', - # cls.redis: 'redis-cli -h {hostname} -p {port} -a {password}', - } - command = commands.get(name) - if isinstance(command, dict): - command = command.get(os, command.get('default')) - return command - - -class AppletMethod: - @classmethod - def get_methods(cls): - from .models import Applet - applets = Applet.objects.all() - methods = defaultdict(list) - for applet in applets: - for protocol in applet.protocols: - methods[protocol].append({ - 'value': applet.name, - 'label': applet.display_name, - 'icon': applet.icon, - }) - return methods - - class TerminalType(TextChoices): koko = 'koko', 'KoKo' guacamole = 'guacamole', 'Guacamole' @@ -181,107 +56,3 @@ class TerminalType(TextChoices): @classmethod def types(cls): return set(dict(cls.choices).keys()) - - @classmethod - def protocols(cls): - protocols = { - cls.koko: { - 'web_methods': [WebMethod.web_cli, WebMethod.web_sftp], - 'listen': [Protocol.ssh, Protocol.http], - 'support': [ - Protocol.ssh, Protocol.telnet, - Protocol.mysql, Protocol.postgresql, - Protocol.oracle, Protocol.sqlserver, - Protocol.mariadb, Protocol.redis, - Protocol.mongodb, Protocol.k8s, - ], - 'match': 'm2m' - }, - cls.omnidb: { - 'web_methods': [WebMethod.web_gui], - 'listen': [Protocol.http], - 'support': [ - Protocol.mysql, Protocol.postgresql, Protocol.oracle, - Protocol.sqlserver, Protocol.mariadb - ], - 'match': 'm2m' - }, - cls.lion: { - 'web_methods': [WebMethod.web_gui], - 'listen': [Protocol.http], - 'support': [Protocol.rdp, Protocol.vnc], - 'match': 'm2m' - }, - cls.magnus: { - 'listen': [], - 'support': [ - Protocol.mysql, Protocol.postgresql, - Protocol.oracle, Protocol.mariadb - ], - 'match': 'map' - }, - cls.razor: { - 'listen': [Protocol.rdp], - 'support': [Protocol.rdp], - 'match': 'map' - }, - } - return protocols - - @classmethod - def get_connect_method(cls, name, protocol, os='linux'): - methods = cls.get_protocols_connect_methods(os) - protocol_methods = methods.get(protocol, []) - for method in protocol_methods: - if method['value'] == name: - return method - return None - - @classmethod - def get_protocols_connect_methods(cls, os): - methods = defaultdict(list) - web_methods = WebMethod.get_methods() - native_methods = NativeClient.get_methods(os) - applet_methods = AppletMethod.get_methods() - - for component, component_protocol in cls.protocols().items(): - support = component_protocol['support'] - - for protocol in support: - if component_protocol['match'] == 'map': - listen = [protocol] - else: - listen = component_protocol['listen'] - - for listen_protocol in listen: - # Native method - methods[protocol.value].extend([ - { - 'component': component.value, - 'type': 'native', - 'endpoint_protocol': listen_protocol, - **method - } - for method in native_methods[listen_protocol] - ]) - - protocol_web_methods = set(web_methods.get(protocol, [])) \ - & set(component_protocol.get('web_methods', [])) - methods[protocol.value].extend([ - { - 'component': component.value, - 'type': 'web', - 'endpoint_protocol': 'http', - 'value': method.value, - 'label': method.label, - } - for method in protocol_web_methods - ]) - - for protocol, applet_methods in applet_methods.items(): - for method in applet_methods: - method['type'] = 'applet' - method['listen'] = 'rdp' - method['component'] = cls.tinker.value - methods[protocol].extend(applet_methods) - return methods diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index cf854b036..bfe0e5e67 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -1,14 +1,15 @@ -import yaml import os.path +import random +import yaml from django.conf import settings +from django.core.cache import cache from django.core.files.storage import default_storage from django.db import models from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel - __all__ = ['Applet', 'AppletPublication'] @@ -53,10 +54,43 @@ class Applet(JMSBaseModel): return None return os.path.join(settings.MEDIA_URL, 'applets', self.name, 'icon.png') + def select_host_account(self): + hosts = list(self.hosts.all()) + if not hosts: + return None + + host = random.choice(hosts) + using_keys = cache.keys('host_accounts_{}_*'.format(host.id)) or [] + accounts_used = cache.get_many(using_keys) + accounts = host.accounts.all().exclude(username__in=accounts_used) + + if not accounts: + accounts = host.accounts.all() + if not accounts: + return None + + account = random.choice(accounts) + ttl = 60 * 60 * 24 + lock_key = 'applet_host_accounts_{}_{}'.format(host.id, account.username) + cache.set(lock_key, account.username, ttl) + return { + 'host': host, + 'account': account, + 'lock_key': lock_key, + 'ttl': ttl + } + + @staticmethod + def release_host_and_account(host_id, username): + key = 'applet_host_accounts_{}_{}'.format(host_id, username) + cache.delete(key) + class AppletPublication(JMSBaseModel): - applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet')) - host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host')) + applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', + verbose_name=_('Applet')) + host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', + verbose_name=_('Host')) status = models.CharField(max_length=16, default='ready', verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index 7868159a8..d98e21eee 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -4,17 +4,18 @@ from django.db.models.signals import post_save, post_delete from django.db.utils import ProgrammingError from django.dispatch import receiver +from django.utils.functional import LazyObject +from assets.models import Asset from common.signals import django_ready from common.utils import get_logger +from common.utils.connection import RedisPubSub from orgs.utils import tmp_to_builtin_org -from assets.models import Asset -from .utils import db_port_manager, DBPortManager from .models import Applet, AppletHost +from .utils import db_port_manager, DBPortManager db_port_manager: DBPortManager - logger = get_logger(__file__) @@ -27,6 +28,8 @@ def on_applet_host_create(sender, instance, created=False, **kwargs): with tmp_to_builtin_org(system=1): instance.generate_accounts() + applet_host_change_pub_sub.publish(True) + @receiver(post_save, sender=Applet) def on_applet_create(sender, instance, created=False, **kwargs): @@ -35,6 +38,8 @@ def on_applet_create(sender, instance, created=False, **kwargs): hosts = AppletHost.objects.all() instance.hosts.set(hosts) + applet_host_change_pub_sub.publish(True) + @receiver(django_ready) def init_db_port_mapper(sender, **kwargs): @@ -59,3 +64,22 @@ def on_db_app_delete(sender, instance, **kwargs): if not instance.category != 'database': return db_port_manager.pop(instance) + + +class AppletHostPubSub(LazyObject): + def _setup(self): + self._wrapped = RedisPubSub('fm.applet_host_change') + + +@receiver(django_ready) +def subscribe_applet_host_change(sender, **kwargs): + logger.debug("Start subscribe for expire node assets id mapping from memory") + + def on_change(message): + from terminal.connect_methods import ConnectMethodUtil + ConnectMethodUtil.refresh_methods() + + applet_host_change_pub_sub.subscribe(on_change) + + +applet_host_change_pub_sub = AppletHostPubSub() From 02a03e1a28d9ba4da19f923d68f2820184f4a10f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Dec 2022 16:02:18 +0800 Subject: [PATCH 007/132] perf: merge i18n --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 3620 ++++++++++++++++---------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 3614 ++++++++++++++----------- 4 files changed, 4317 insertions(+), 2925 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 315c546b7..8622a74d1 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:5cc8f923c01a87b106a54f8a7c53abdb98683b1c4b4f975f9a3ae8af5fae73c8 -size 373 +oid sha256:03b1fcb75dae7e070f662f2ad554774d51311d2561367f5d28addc3b14899195 +size 119767 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a892b45d6..d5e72d0e0 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -20,20 +20,22 @@ msgstr "" #: acls/apps.py:7 msgid "Acls" -msgstr "" +msgstr "Acls" #: acls/models/base.py:20 tickets/const.py:45 #: tickets/templates/tickets/approve_check_password.html:49 msgid "Reject" -msgstr "" +msgstr "拒否" #: acls/models/base.py:21 +#, fuzzy msgid "Accept" -msgstr "" +msgstr "受け入れられる" #: acls/models/base.py:22 +#, fuzzy msgid "Review" -msgstr "" +msgstr "レビュー担当者" #: acls/models/base.py:71 acls/models/command_acl.py:22 #: acls/serializers/base.py:34 applications/models.py:10 @@ -54,36 +56,36 @@ msgstr "" #: users/models/group.py:15 users/models/user.py:675 #: xpack/plugins/cloud/models.py:30 msgid "Name" -msgstr "" +msgstr "名前" #: acls/models/base.py:73 assets/models/_user.py:47 #: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:89 msgid "Priority" -msgstr "" +msgstr "優先順位" #: acls/models/base.py:74 assets/models/_user.py:47 #: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:90 msgid "1-100, the lower the value will be match first" -msgstr "" +msgstr "1-100、低い値は最初に一致します" #: acls/models/base.py:77 acls/serializers/base.py:63 #: assets/models/cmd_filter.py:86 audits/models.py:51 audits/serializers.py:75 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" -msgstr "" +msgstr "アクション" #: acls/models/base.py:78 acls/serializers/base.py:59 #: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:91 #: authentication/serializers/connect_token_secret.py:79 msgid "Reviewers" -msgstr "" +msgstr "レビュー担当者" #: acls/models/base.py:79 authentication/models/access_key.py:17 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 #: tickets/const.py:37 msgid "Active" -msgstr "" +msgstr "アクティブ" #: acls/models/base.py:80 acls/models/command_acl.py:29 #: applications/models.py:19 assets/models/_user.py:40 @@ -105,7 +107,7 @@ msgstr "" #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:121 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" -msgstr "" +msgstr "コメント" #: acls/models/base.py:92 acls/models/login_acl.py:13 #: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 @@ -123,7 +125,7 @@ msgstr "" #: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:907 #: users/models/user.py:938 users/serializers/group.py:19 msgid "User" -msgstr "" +msgstr "ユーザー" #: acls/models/base.py:94 acls/serializers/base.py:56 #: assets/models/account.py:51 assets/models/asset/common.py:83 @@ -142,7 +144,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:172 #: xpack/plugins/cloud/models.py:222 msgid "Asset" -msgstr "" +msgstr "資産" #: acls/models/base.py:96 acls/serializers/base.py:57 #: assets/models/account.py:61 @@ -152,7 +154,7 @@ msgstr "" #: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:71 msgid "Account" -msgstr "" +msgstr "アカウント" #: acls/models/command_acl.py:17 assets/models/cmd_filter.py:65 #: terminal/backends/command/serializers.py:15 @@ -160,11 +162,11 @@ msgstr "" #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" -msgstr "" +msgstr "コマンド" #: acls/models/command_acl.py:18 assets/models/cmd_filter.py:64 msgid "Regex" -msgstr "" +msgstr "正規情報" #: acls/models/command_acl.py:25 acls/serializers/command_acl.py:14 #: applications/models.py:15 assets/models/_user.py:46 @@ -182,65 +184,68 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" -msgstr "" +msgstr "タイプ" #: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 #: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 msgid "Content" -msgstr "" +msgstr "コンテンツ" #: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 msgid "One line one command" -msgstr "" +msgstr "1行1コマンド" #: acls/models/command_acl.py:28 assets/models/cmd_filter.py:85 msgid "Ignore case" -msgstr "" +msgstr "家を無視する" #: acls/models/command_acl.py:35 acls/serializers/command_acl.py:24 #: authentication/serializers/connect_token_secret.py:76 +#, fuzzy msgid "Command group" -msgstr "" +msgstr "コマンドレコード" #: acls/models/command_acl.py:88 msgid "The generated regular expression is incorrect: {}" -msgstr "" +msgstr "生成された正規表現が正しくありません: {}" #: acls/models/command_acl.py:98 +#, fuzzy msgid "Commands" -msgstr "" +msgstr "コマンド" #: acls/models/command_acl.py:102 +#, fuzzy msgid "Command acl" -msgstr "" +msgstr "コマンド" #: acls/models/command_acl.py:111 tickets/const.py:11 msgid "Command confirm" -msgstr "" +msgstr "コマンドの確認" #: acls/models/login_acl.py:16 msgid "Rule" -msgstr "" +msgstr "ルール" #: acls/models/login_acl.py:19 msgid "Login acl" -msgstr "" +msgstr "ログインacl" #: acls/models/login_acl.py:54 tickets/const.py:10 msgid "Login confirm" -msgstr "" +msgstr "ログイン確認" #: acls/models/login_asset_acl.py:10 msgid "Login asset acl" -msgstr "" +msgstr "ログインasset acl" #: acls/models/login_asset_acl.py:20 tickets/const.py:12 msgid "Login asset confirm" -msgstr "" +msgstr "ログイン資産の確認" #: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 msgid "Format for comma-delimited string, with * indicating a match all. " -msgstr "" +msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" #: acls/serializers/base.py:18 acls/serializers/base.py:49 #: assets/models/_user.py:34 assets/models/base.py:65 @@ -255,7 +260,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/asset.py:196 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Username" -msgstr "" +msgstr "ユーザー名" #: acls/serializers/base.py:25 msgid "" @@ -263,23 +268,27 @@ msgid "" "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" "db8:1a:1110::/64 (Domain name support)" msgstr "" +"コンマ区切り文字列の形式。* はすべて一致することを示します。例: " +"192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" +"db8:1a:1110:::/64 (ドメイン名サポート)" #: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 +#, fuzzy msgid "IP/Host" -msgstr "" +msgstr "ホスト" #: acls/serializers/base.py:90 tickets/serializers/ticket/ticket.py:79 msgid "The organization `{}` does not exist" -msgstr "" +msgstr "組織 '{}'は存在しません" #: acls/serializers/base.py:96 msgid "None of the reviewers belong to Organization `{}`" -msgstr "" +msgstr "いずれのレビューアも組織 '{}' に属していません" #: acls/serializers/rules/rules.py:20 #: xpack/plugins/cloud/serializers/task.py:23 msgid "IP address invalid: `{}`" -msgstr "" +msgstr "IPアドレスが無効: '{}'" #: acls/serializers/rules/rules.py:25 msgid "" @@ -287,6 +296,9 @@ msgid "" "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" "db8:1a:1110::/64 " msgstr "" +"コンマ区切り文字列の形式。* はすべて一致することを示します。例: " +"192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" +"db8:1a:1110::/64" #: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 #: authentication/templates/authentication/_msg_oauth_bind.html:12 @@ -294,15 +306,15 @@ msgstr "" #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 #: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54 msgid "IP" -msgstr "" +msgstr "IP" #: acls/serializers/rules/rules.py:35 msgid "Time Period" -msgstr "" +msgstr "期間" #: applications/apps.py:9 msgid "Applications" -msgstr "" +msgstr "アプリケーション" #: applications/models.py:12 assets/models/label.py:20 #: assets/models/platform.py:73 assets/serializers/asset/common.py:62 @@ -311,72 +323,75 @@ msgstr "" #: settings/models.py:35 tickets/models/ticket/apply_application.py:13 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" -msgstr "" +msgstr "カテゴリ" #: applications/models.py:17 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/serializers/account.py:64 msgid "Attrs" -msgstr "" +msgstr "ツールバーの" #: applications/models.py:23 xpack/plugins/change_auth_plan/models/app.py:31 msgid "Application" -msgstr "" +msgstr "アプリケーション" #: applications/models.py:27 msgid "Can match application" -msgstr "" +msgstr "アプリケーションを一致させることができます" #: applications/serializers/attrs/application_type/clickhouse.py:11 #: assets/models/asset/common.py:82 assets/models/platform.py:22 #: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" -msgstr "" +msgstr "ポート" #: applications/serializers/attrs/application_type/clickhouse.py:13 msgid "" "Typically, the port is 9000,the HTTP interface and the native interface use " "different ports" msgstr "" +"デフォルトポートは9000で、HTTPインタフェースとネイティブインタフェースは異な" +"るポートを使用する" #: assets/api/automations/base.py:76 #: xpack/plugins/change_auth_plan/api/asset.py:94 msgid "The parameter 'action' must be [{}]" -msgstr "" +msgstr "パラメータ 'action' は [{}] でなければなりません。" #: assets/api/domain.py:56 msgid "Number required" -msgstr "" +msgstr "必要な数" #: assets/api/node.py:62 msgid "You can't update the root node name" -msgstr "" +msgstr "ルートノード名を更新できません" #: assets/api/node.py:69 msgid "You can't delete the root node ({})" -msgstr "" +msgstr "ルートノード ({}) を削除できません。" #: assets/api/node.py:72 msgid "Deletion failed and the node contains assets" -msgstr "" +msgstr "削除に失敗し、ノードにアセットが含まれています。" #: assets/apps.py:9 msgid "App assets" -msgstr "" +msgstr "アプリ資産" #: assets/automations/base/manager.py:123 +#, fuzzy msgid "{} disabled" -msgstr "" +msgstr "無効" #: assets/const/account.py:6 audits/const.py:6 audits/const.py:64 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/utils.py:84 msgid "Unknown" -msgstr "" +msgstr "不明" #: assets/const/account.py:7 msgid "Ok" -msgstr "" +msgstr "OK" #: assets/const/account.py:8 #: assets/serializers/automations/change_secret.py:118 @@ -385,7 +400,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:41 msgid "Failed" -msgstr "" +msgstr "失敗しました" #: assets/const/account.py:12 assets/models/_user.py:35 #: audits/signal_handlers.py:49 authentication/confirm/password.py:9 @@ -402,44 +417,50 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/base.py:73 #: xpack/plugins/cloud/serializers/account_attrs.py:28 msgid "Password" -msgstr "" +msgstr "パスワード" #: assets/const/account.py:13 +#, fuzzy msgid "SSH key" -msgstr "" +msgstr "SSHキー" #: assets/const/account.py:14 authentication/models/access_key.py:33 msgid "Access key" -msgstr "" +msgstr "アクセスキー" #: assets/const/account.py:15 assets/models/_user.py:38 #: authentication/models/sso_token.py:14 msgid "Token" -msgstr "" +msgstr "トークン" #: assets/const/automation.py:13 msgid "Ping" msgstr "" #: assets/const/automation.py:14 +#, fuzzy msgid "Gather facts" -msgstr "" +msgstr "アカウントを集める" #: assets/const/automation.py:15 +#, fuzzy msgid "Create account" -msgstr "" +msgstr "アカウントを集める" #: assets/const/automation.py:16 +#, fuzzy msgid "Change secret" -msgstr "" +msgstr "秘密を改める" #: assets/const/automation.py:17 +#, fuzzy msgid "Verify account" -msgstr "" +msgstr "パスワード/キーの確認" #: assets/const/automation.py:18 +#, fuzzy msgid "Gather accounts" -msgstr "" +msgstr "アカウントを集める" #: assets/const/automation.py:38 assets/serializers/account/base.py:26 msgid "Specific" @@ -448,34 +469,34 @@ msgstr "" #: assets/const/automation.py:39 ops/const.py:20 #: xpack/plugins/change_auth_plan/models/base.py:28 msgid "All assets use the same random password" -msgstr "" +msgstr "すべての資産は同じランダムパスワードを使用します" #: assets/const/automation.py:40 ops/const.py:21 #: xpack/plugins/change_auth_plan/models/base.py:29 msgid "All assets use different random password" -msgstr "" +msgstr "すべての資産は異なるランダムパスワードを使用します" #: assets/const/automation.py:44 ops/const.py:13 #: xpack/plugins/change_auth_plan/models/asset.py:30 msgid "Append SSH KEY" -msgstr "" +msgstr "追加" #: assets/const/automation.py:45 ops/const.py:14 #: xpack/plugins/change_auth_plan/models/asset.py:31 msgid "Empty and append SSH KEY" -msgstr "" +msgstr "すべてクリアして追加" #: assets/const/automation.py:46 ops/const.py:15 #: xpack/plugins/change_auth_plan/models/asset.py:32 msgid "Replace (The key generated by JumpServer) " -msgstr "" +msgstr "置換(JumpServerによって生成された鍵)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:16 #: settings/serializers/auth/sms.py:67 terminal/models/applet/applet.py:59 #: terminal/models/component/endpoint.py:13 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" -msgstr "" +msgstr "ホスト" #: assets/const/category.py:12 msgid "Device" @@ -484,11 +505,12 @@ msgstr "" #: assets/const/category.py:13 assets/models/asset/database.py:8 #: assets/models/asset/database.py:34 msgid "Database" -msgstr "" +msgstr "データベース" #: assets/const/category.py:14 +#, fuzzy msgid "Cloud service" -msgstr "" +msgstr "クラウドセンター" #: assets/const/category.py:15 audits/const.py:62 #: terminal/models/applet/applet.py:18 @@ -498,11 +520,12 @@ msgstr "" #: assets/const/device.py:7 terminal/models/applet/applet.py:17 #: tickets/const.py:8 msgid "General" -msgstr "" +msgstr "一般" #: assets/const/device.py:8 +#, fuzzy msgid "Switch" -msgstr "" +msgstr "から切り替え" #: assets/const/device.py:9 msgid "Router" @@ -513,36 +536,37 @@ msgid "Firewall" msgstr "" #: assets/const/web.py:7 +#, fuzzy msgid "Website" -msgstr "" +msgstr "ウェブサイトのアイコン" #: assets/models/_user.py:24 msgid "Automatic managed" -msgstr "" +msgstr "自動管理" #: assets/models/_user.py:25 msgid "Manually input" -msgstr "" +msgstr "手動入力" #: assets/models/_user.py:29 msgid "Common user" -msgstr "" +msgstr "共通ユーザー" #: assets/models/_user.py:30 msgid "Admin user" -msgstr "" +msgstr "管理ユーザー" #: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" -msgstr "" +msgstr "SSH秘密鍵" #: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 #: xpack/plugins/change_auth_plan/models/asset.py:127 #: xpack/plugins/change_auth_plan/models/asset.py:203 msgid "SSH public key" -msgstr "" +msgstr "SSHパブリックキー" #: assets/models/_user.py:41 assets/models/automations/base.py:92 #: assets/models/cmd_filter.py:46 assets/models/domain.py:23 @@ -552,13 +576,13 @@ msgstr "" #: users/models/group.py:18 users/models/user.py:939 #: xpack/plugins/change_auth_plan/models/base.py:45 msgid "Date created" -msgstr "" +msgstr "作成された日付" #: assets/models/_user.py:42 assets/models/cmd_filter.py:47 #: assets/models/gathered_user.py:20 common/db/models.py:78 #: common/mixins/models.py:51 xpack/plugins/change_auth_plan/models/base.py:46 msgid "Date updated" -msgstr "" +msgstr "更新日" #: assets/models/_user.py:43 assets/models/base.py:73 #: assets/models/cmd_filter.py:49 assets/models/cmd_filter.py:96 @@ -567,128 +591,134 @@ msgstr "" #: users/models/user.py:722 users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 msgid "Created by" -msgstr "" +msgstr "によって作成された" #: assets/models/_user.py:45 msgid "Username same with user" -msgstr "" +msgstr "ユーザーと同じユーザー名" #: assets/models/_user.py:48 authentication/models/connection_token.py:34 #: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 #: terminal/serializers/session.py:18 terminal/serializers/session.py:32 #: terminal/serializers/storage.py:68 msgid "Protocol" -msgstr "" +msgstr "プロトコル" #: assets/models/_user.py:49 msgid "Auto push" -msgstr "" +msgstr "オートプッシュ" #: assets/models/_user.py:50 msgid "Sudo" -msgstr "" +msgstr "すど" #: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 msgid "Shell" -msgstr "" +msgstr "シェル" #: assets/models/_user.py:52 msgid "Login mode" -msgstr "" +msgstr "ログインモード" #: assets/models/_user.py:53 msgid "SFTP Root" -msgstr "" +msgstr "SFTPルート" #: assets/models/_user.py:54 msgid "Home" -msgstr "" +msgstr "ホーム" #: assets/models/_user.py:55 msgid "System groups" -msgstr "" +msgstr "システムグループ" #: assets/models/_user.py:58 msgid "User switch" -msgstr "" +msgstr "ユーザースイッチ" #: assets/models/_user.py:59 msgid "Switch from" -msgstr "" +msgstr "から切り替え" #: assets/models/_user.py:65 audits/models.py:35 #: xpack/plugins/change_auth_plan/models/app.py:35 #: xpack/plugins/change_auth_plan/models/app.py:146 msgid "System user" -msgstr "" +msgstr "システムユーザー" #: assets/models/_user.py:67 msgid "Can match system user" -msgstr "" +msgstr "システムユーザーに一致できます" #: assets/models/account.py:45 common/db/fields.py:232 #: settings/serializers/terminal.py:14 msgid "All" -msgstr "" +msgstr "すべて" #: assets/models/account.py:46 +#, fuzzy msgid "Manual input" -msgstr "" +msgstr "手動入力" #: assets/models/account.py:47 +#, fuzzy msgid "Dynamic user" -msgstr "" +msgstr "動的コード" #: assets/models/account.py:55 #: authentication/serializers/connect_token_secret.py:47 +#, fuzzy msgid "Su from" -msgstr "" +msgstr "から切り替え" #: assets/models/account.py:57 settings/serializers/auth/cas.py:20 #: terminal/models/applet/applet.py:22 msgid "Version" -msgstr "" +msgstr "バージョン" #: assets/models/account.py:67 msgid "Can view asset account secret" -msgstr "" +msgstr "資産アカウントの秘密を表示できます" #: assets/models/account.py:68 msgid "Can change asset account secret" -msgstr "" +msgstr "資産口座の秘密を変更できます" #: assets/models/account.py:69 msgid "Can view asset history account" -msgstr "" +msgstr "資産履歴アカウントを表示できます" #: assets/models/account.py:70 msgid "Can view asset history account secret" -msgstr "" +msgstr "資産履歴アカウントパスワードを表示できます" #: assets/models/account.py:93 assets/serializers/account/account.py:15 +#, fuzzy msgid "Account template" -msgstr "" +msgstr "アカウント名" #: assets/models/account.py:98 +#, fuzzy msgid "Can view asset account template secret" -msgstr "" +msgstr "資産アカウントの秘密を表示できます" #: assets/models/account.py:99 +#, fuzzy msgid "Can change asset account template secret" -msgstr "" +msgstr "資産口座の秘密を変更できます" #: assets/models/asset/common.py:93 assets/models/platform.py:110 #: assets/serializers/asset/common.py:65 #: perms/serializers/user_permission.py:21 #: xpack/plugins/cloud/serializers/account_attrs.py:179 msgid "Platform" -msgstr "" +msgstr "プラットフォーム" #: assets/models/asset/common.py:95 assets/models/domain.py:26 #: assets/serializers/asset/common.py:64 #: authentication/serializers/connect_token_secret.py:105 msgid "Domain" -msgstr "" +msgstr "ドメイン" #: assets/models/asset/common.py:97 assets/models/automations/base.py:18 #: assets/models/cmd_filter.py:37 assets/serializers/asset/common.py:66 @@ -697,88 +727,95 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" -msgstr "" +msgstr "ノード" #: assets/models/asset/common.py:98 assets/models/automations/base.py:21 #: assets/models/base.py:71 assets/models/cmd_filter.py:44 #: assets/models/label.py:21 terminal/models/applet/applet.py:25 #: users/serializers/user.py:202 msgid "Is active" -msgstr "" +msgstr "アクティブです。" #: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 msgid "Labels" -msgstr "" +msgstr "ラベル" #: assets/models/asset/common.py:215 msgid "Can refresh asset hardware info" -msgstr "" +msgstr "資産ハードウェア情報を更新できます" #: assets/models/asset/common.py:216 msgid "Can test asset connectivity" -msgstr "" +msgstr "資産接続をテストできます" #: assets/models/asset/common.py:217 +#, fuzzy msgid "Can push account to asset" -msgstr "" +msgstr "システムユーザーを資産にプッシュできます" #: assets/models/asset/common.py:218 msgid "Can match asset" -msgstr "" +msgstr "アセットを一致させることができます" #: assets/models/asset/common.py:219 msgid "Add asset to node" -msgstr "" +msgstr "ノードにアセットを追加する" #: assets/models/asset/common.py:220 msgid "Move asset to node" -msgstr "" +msgstr "アセットをノードに移動する" #: assets/models/asset/database.py:9 settings/serializers/email.py:37 msgid "Use SSL" -msgstr "" +msgstr "SSLの使用" #: assets/models/asset/database.py:10 +#, fuzzy msgid "CA cert" -msgstr "" +msgstr "SP 証明書" #: assets/models/asset/database.py:11 +#, fuzzy msgid "Client cert" -msgstr "" +msgstr "クライアント秘密" #: assets/models/asset/database.py:12 +#, fuzzy msgid "Client key" -msgstr "" +msgstr "クライアント" #: assets/models/asset/database.py:13 msgid "Allow invalid cert" -msgstr "" +msgstr "証明書チェックを無視" #: assets/models/asset/web.py:9 audits/const.py:68 #: terminal/serializers/applet_host.py:25 msgid "Disabled" -msgstr "" +msgstr "無効" #: assets/models/asset/web.py:10 settings/serializers/auth/base.py:10 #: settings/serializers/basic.py:27 msgid "Basic" -msgstr "" +msgstr "基本" #: assets/models/asset/web.py:11 assets/models/asset/web.py:17 msgid "Script" msgstr "" #: assets/models/asset/web.py:13 +#, fuzzy msgid "Autofill" -msgstr "" +msgstr "自動" #: assets/models/asset/web.py:14 assets/serializers/platform.py:30 +#, fuzzy msgid "Username selector" -msgstr "" +msgstr "ユーザー名のプロパティ" #: assets/models/asset/web.py:15 assets/serializers/platform.py:33 +#, fuzzy msgid "Password selector" -msgstr "" +msgstr "パスワードルール" #: assets/models/asset/web.py:16 assets/serializers/platform.py:36 msgid "Submit selector" @@ -788,7 +825,7 @@ msgstr "" #: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 #: perms/serializers/permission.py:32 rbac/tree.py:37 msgid "Accounts" -msgstr "" +msgstr "アカウント" #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 ops/models/base.py:17 @@ -796,11 +833,12 @@ msgstr "" #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" -msgstr "" +msgstr "資産" #: assets/models/automations/base.py:82 assets/models/automations/base.py:89 +#, fuzzy msgid "Automation task" -msgstr "" +msgstr "自動管理" #: assets/models/automations/base.py:91 audits/models.py:129 #: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 @@ -809,7 +847,7 @@ msgstr "" #: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:20 #: xpack/plugins/cloud/models.py:174 xpack/plugins/cloud/models.py:226 msgid "Status" -msgstr "" +msgstr "ステータス" #: assets/models/automations/base.py:93 assets/models/backup.py:76 #: audits/models.py:41 ops/models/base.py:55 ops/models/celery.py:59 @@ -821,19 +859,20 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:199 #: xpack/plugins/gathered_user/models.py:71 msgid "Date start" -msgstr "" +msgstr "開始日" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 #: ops/models/celery.py:60 ops/models/job.py:110 #: terminal/models/applet/host.py:106 msgid "Date finished" -msgstr "" +msgstr "終了日" #: assets/models/automations/base.py:96 #: assets/serializers/automations/base.py:39 +#, fuzzy msgid "Automation snapshot" -msgstr "" +msgstr "製造オーダスナップショット" #: assets/models/automations/base.py:100 assets/models/backup.py:87 #: assets/serializers/account/backup.py:37 @@ -841,22 +880,25 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" -msgstr "" +msgstr "トリガーモード" #: assets/models/automations/base.py:104 #: assets/serializers/automations/change_secret.py:103 +#, fuzzy msgid "Automation task execution" -msgstr "" +msgstr "インスタンスタスクの同期実行" #: assets/models/automations/change_secret.py:15 assets/models/base.py:67 #: assets/serializers/account/account.py:97 assets/serializers/base.py:13 +#, fuzzy msgid "Secret type" -msgstr "" +msgstr "秘密キー" #: assets/models/automations/change_secret.py:19 #: assets/serializers/automations/change_secret.py:25 +#, fuzzy msgid "Secret strategy" -msgstr "" +msgstr "秘密キー" #: assets/models/automations/change_secret.py:21 #: assets/models/automations/change_secret.py:57 assets/models/base.py:69 @@ -864,16 +906,17 @@ msgstr "" #: authentication/templates/authentication/_access_key_modal.html:31 #: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:19 msgid "Secret" -msgstr "" +msgstr "ひみつ" #: assets/models/automations/change_secret.py:22 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" -msgstr "" +msgstr "パスワードルール" #: assets/models/automations/change_secret.py:25 +#, fuzzy msgid "SSH key change strategy" -msgstr "" +msgstr "SSHキー戦略" #: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 #: assets/serializers/account/backup.py:30 @@ -882,27 +925,32 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/asset.py:63 #: xpack/plugins/change_auth_plan/serializers/base.py:45 msgid "Recipient" -msgstr "" +msgstr "受信者" #: assets/models/automations/change_secret.py:34 +#, fuzzy msgid "Change secret automation" -msgstr "" +msgstr "セキュリティ設定を変更できます" #: assets/models/automations/change_secret.py:56 +#, fuzzy msgid "Old secret" -msgstr "" +msgstr "OTP 秘密" #: assets/models/automations/change_secret.py:58 +#, fuzzy msgid "Date started" -msgstr "" +msgstr "開始日" #: assets/models/automations/change_secret.py:61 common/const/choices.py:20 +#, fuzzy msgid "Error" -msgstr "" +msgstr "企業微信エラー" #: assets/models/automations/change_secret.py:64 +#, fuzzy msgid "Change secret record" -msgstr "" +msgstr "パスワードの変更" #: assets/models/automations/discovery_account.py:8 msgid "Discovery account automation" @@ -910,28 +958,33 @@ msgstr "" #: assets/models/automations/gather_accounts.py:15 #: assets/tasks/gather_accounts.py:28 +#, fuzzy msgid "Gather asset accounts" -msgstr "" +msgstr "アカウントを集める" #: assets/models/automations/gather_facts.py:15 +#, fuzzy msgid "Gather asset facts" -msgstr "" +msgstr "資産ユーザーの収集" #: assets/models/automations/ping.py:15 +#, fuzzy msgid "Ping asset" -msgstr "" +msgstr "ログイン資産" #: assets/models/automations/push_account.py:16 +#, fuzzy msgid "Push asset account" -msgstr "" +msgstr "サービスアカウントです" #: assets/models/automations/verify_account.py:15 +#, fuzzy msgid "Verify asset account" -msgstr "" +msgstr "パスワード/キーの確認" #: assets/models/backup.py:37 assets/models/backup.py:95 msgid "Account backup plan" -msgstr "" +msgstr "アカウントバックアップ計画" #: assets/models/backup.py:79 #: authentication/templates/authentication/_msg_oauth_bind.html:11 @@ -940,11 +993,11 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:200 #: xpack/plugins/gathered_user/models.py:74 msgid "Time" -msgstr "" +msgstr "時間" #: assets/models/backup.py:83 msgid "Account backup snapshot" -msgstr "" +msgstr "アカウントのバックアップスナップショット" #: assets/models/backup.py:90 audits/models.py:124 #: terminal/models/session/sharing.py:108 @@ -952,7 +1005,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:171 #: xpack/plugins/cloud/models.py:178 msgid "Reason" -msgstr "" +msgstr "理由" #: assets/models/backup.py:92 #: assets/serializers/automations/change_secret.py:99 @@ -961,19 +1014,19 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:198 #: xpack/plugins/change_auth_plan/serializers/asset.py:173 msgid "Is success" -msgstr "" +msgstr "成功は" #: assets/models/backup.py:99 msgid "Account backup execution" -msgstr "" +msgstr "アカウントバックアップの実行" #: assets/models/base.py:26 msgid "Connectivity" -msgstr "" +msgstr "接続性" #: assets/models/base.py:28 authentication/models/temp_token.py:12 msgid "Date verified" -msgstr "" +msgstr "確認済みの日付" #: assets/models/base.py:70 msgid "Privileged" @@ -982,229 +1035,245 @@ msgstr "" #: assets/models/cmd_filter.py:33 perms/models/asset_permission.py:56 #: users/models/group.py:31 users/models/user.py:681 msgid "User group" -msgstr "" +msgstr "ユーザーグループ" #: assets/models/cmd_filter.py:57 msgid "Command filter" -msgstr "" +msgstr "コマンドフィルター" #: assets/models/cmd_filter.py:71 msgid "Deny" -msgstr "" +msgstr "拒否" #: assets/models/cmd_filter.py:72 msgid "Allow" -msgstr "" +msgstr "許可" #: assets/models/cmd_filter.py:73 msgid "Reconfirm" -msgstr "" +msgstr "再確認" #: assets/models/cmd_filter.py:77 msgid "Filter" -msgstr "" +msgstr "フィルター" #: assets/models/cmd_filter.py:100 msgid "Command filter rule" -msgstr "" +msgstr "コマンドフィルタルール" #: assets/models/gateway.py:61 authentication/models/connection_token.py:101 +#, fuzzy msgid "No account" -msgstr "" +msgstr "アカウント" #: assets/models/gateway.py:83 -#, python-brace-format +#, fuzzy, python-brace-format msgid "Unable to connect to port {port} on {address}" -msgstr "" +msgstr "{ip} でポート {port} に接続できません" #: assets/models/gateway.py:86 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" -msgstr "" +msgstr "認証に失敗しました" #: assets/models/gateway.py:88 assets/models/gateway.py:115 msgid "Connect failed" -msgstr "" +msgstr "接続に失敗しました" #: assets/models/gathered_user.py:16 msgid "Present" -msgstr "" +msgstr "プレゼント" #: assets/models/gathered_user.py:17 msgid "Date last login" -msgstr "" +msgstr "最終ログイン日" #: assets/models/gathered_user.py:18 msgid "IP last login" -msgstr "" +msgstr "IP最終ログイン" #: assets/models/gathered_user.py:31 msgid "GatherUser" -msgstr "" +msgstr "収集ユーザー" #: assets/models/group.py:30 msgid "Asset group" -msgstr "" +msgstr "資産グループ" #: assets/models/group.py:34 assets/models/platform.py:19 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" -msgstr "" +msgstr "デフォルト" #: assets/models/group.py:34 msgid "Default asset group" -msgstr "" +msgstr "デフォルトアセットグループ" #: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:924 msgid "System" -msgstr "" +msgstr "システム" #: assets/models/label.py:18 assets/models/node.py:553 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 #: authentication/models/connection_token.py:22 #: common/drf/serializers/common.py:82 settings/models.py:34 msgid "Value" -msgstr "" +msgstr "値" #: assets/models/label.py:36 assets/serializers/cagegory.py:6 #: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 #: settings/serializers/sms.py:7 msgid "Label" -msgstr "" +msgstr "ラベル" #: assets/models/node.py:158 msgid "New node" -msgstr "" +msgstr "新しいノード" #: assets/models/node.py:481 msgid "empty" -msgstr "" +msgstr "空" #: assets/models/node.py:552 perms/models/perm_node.py:21 msgid "Key" -msgstr "" +msgstr "キー" #: assets/models/node.py:554 assets/serializers/node.py:20 msgid "Full value" -msgstr "" +msgstr "フルバリュー" #: assets/models/node.py:558 perms/models/perm_node.py:22 msgid "Parent key" -msgstr "" +msgstr "親キー" #: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 #: xpack/plugins/cloud/serializers/task.py:74 msgid "Node" -msgstr "" +msgstr "ノード" #: assets/models/node.py:570 msgid "Can match node" -msgstr "" +msgstr "ノードを一致させることができます" #: assets/models/platform.py:20 +#, fuzzy msgid "Required" -msgstr "" +msgstr "MFAが必要" #: assets/models/platform.py:23 settings/serializers/settings.py:61 #: users/templates/users/reset_password.html:29 msgid "Setting" -msgstr "" +msgstr "設定" #: assets/models/platform.py:42 audits/const.py:69 settings/models.py:37 #: terminal/serializers/applet_host.py:26 msgid "Enabled" -msgstr "" +msgstr "有効化" #: assets/models/platform.py:43 msgid "Ansible config" msgstr "" #: assets/models/platform.py:44 +#, fuzzy msgid "Ping enabled" -msgstr "" +msgstr "MFA有効化" #: assets/models/platform.py:45 msgid "Ping method" msgstr "" #: assets/models/platform.py:46 assets/models/platform.py:56 +#, fuzzy msgid "Gather facts enabled" -msgstr "" +msgstr "資産ユーザーの収集" #: assets/models/platform.py:47 assets/models/platform.py:58 +#, fuzzy msgid "Gather facts method" -msgstr "" +msgstr "資産ユーザーの収集" #: assets/models/platform.py:48 +#, fuzzy msgid "Push account enabled" -msgstr "" +msgstr "MFAが有効化されていません" #: assets/models/platform.py:49 msgid "Push account method" msgstr "" #: assets/models/platform.py:50 +#, fuzzy msgid "Change password enabled" -msgstr "" +msgstr "パスワードの変更" #: assets/models/platform.py:52 +#, fuzzy msgid "Change password method" -msgstr "" +msgstr "パスワードの変更" #: assets/models/platform.py:53 +#, fuzzy msgid "Verify account enabled" -msgstr "" +msgstr "サービスアカウントキー" #: assets/models/platform.py:55 +#, fuzzy msgid "Verify account method" -msgstr "" +msgstr "パスワード/キーの確認" #: assets/models/platform.py:75 tickets/models/ticket/general.py:299 msgid "Meta" -msgstr "" +msgstr "メタ" #: assets/models/platform.py:76 msgid "Internal" -msgstr "" +msgstr "内部" #: assets/models/platform.py:80 assets/serializers/platform.py:97 msgid "Charset" -msgstr "" +msgstr "シャーセット" #: assets/models/platform.py:82 +#, fuzzy msgid "Domain enabled" -msgstr "" +msgstr "ドメイン名" #: assets/models/platform.py:83 +#, fuzzy msgid "Protocols enabled" -msgstr "" +msgstr "プロトコル" #: assets/models/platform.py:85 +#, fuzzy msgid "Su enabled" -msgstr "" +msgstr "MFA有効化" #: assets/models/platform.py:86 msgid "SU method" msgstr "" #: assets/models/platform.py:88 assets/serializers/platform.py:104 +#, fuzzy msgid "Automation" -msgstr "" +msgstr "自動管理" #: assets/models/utils.py:19 #, python-format msgid "%(value)s is not an even number" -msgstr "" +msgstr "%(value)s は偶数ではありません" #: assets/notifications.py:8 msgid "Notification of account backup route task results" -msgstr "" +msgstr "アカウントバックアップルートタスクの結果の通知" #: assets/notifications.py:18 msgid "" "{} - The account backup passage task has been completed. See the attachment " "for details" msgstr "" +"{} -アカウントバックアップの通過タスクが完了しました。詳細は添付ファイルをご" +"覧ください" #: assets/notifications.py:20 msgid "" @@ -1212,17 +1281,20 @@ msgid "" "password has not been set - please go to personal information -> file " "encryption password to set the encryption password" msgstr "" +"{} -アカウントのバックアップ通過タスクが完了しました: 暗号化パスワードが設定" +"されていません-個人情報にアクセスしてください-> ファイル暗号化パスワードを設" +"定してください暗号化パスワード" #: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 msgid "Notification of implementation result of encryption change plan" -msgstr "" +msgstr "暗号化変更プランの実装結果の通知" #: assets/notifications.py:41 #: xpack/plugins/change_auth_plan/notifications.py:18 msgid "" "{} - The encryption change task has been completed. See the attachment for " "details" -msgstr "" +msgstr "{} -暗号化変更タスクが完了しました。詳細は添付ファイルをご覧ください" #: assets/notifications.py:42 #: xpack/plugins/change_auth_plan/notifications.py:19 @@ -1231,14 +1303,17 @@ msgid "" "has not been set - please go to personal information -> file encryption " "password to set the encryption password" msgstr "" +"{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" +"情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" #: assets/serializers/account/account.py:18 msgid "Push now" msgstr "" #: assets/serializers/account/account.py:20 +#, fuzzy msgid "Has secret" -msgstr "" +msgstr "ひみつ" #: assets/serializers/account/account.py:27 msgid "Account template not found" @@ -1249,13 +1324,13 @@ msgstr "" #: settings/serializers/auth/ldap.py:66 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" -msgstr "" +msgstr "定期的なパフォーマンス" #: assets/serializers/account/backup.py:31 #: assets/serializers/automations/change_secret.py:41 #: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" -msgstr "" +msgstr "現在、メール送信のみがサポートされています" #: assets/serializers/asset/common.py:68 assets/serializers/platform.py:102 #: authentication/serializers/connect_token_secret.py:27 @@ -1263,95 +1338,97 @@ msgstr "" #: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 #: xpack/plugins/cloud/serializers/task.py:43 msgid "Protocols" -msgstr "" +msgstr "プロトコル" #: assets/serializers/asset/common.py:88 msgid "Address" -msgstr "" +msgstr "アドレス" #: assets/serializers/asset/common.py:156 +#, fuzzy msgid "Platform not exist" -msgstr "" +msgstr "アプリが存在しません" #: assets/serializers/asset/common.py:172 +#, fuzzy msgid "Protocol is required: {}" -msgstr "" +msgstr "プロトコル重複: {}" #: assets/serializers/asset/host.py:12 msgid "Vendor" -msgstr "" +msgstr "ベンダー" #: assets/serializers/asset/host.py:13 msgid "Model" -msgstr "" +msgstr "モデル" #: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 msgid "Serial number" -msgstr "" +msgstr "シリアル番号" #: assets/serializers/asset/host.py:16 msgid "CPU model" -msgstr "" +msgstr "CPU モデル" #: assets/serializers/asset/host.py:17 msgid "CPU count" -msgstr "" +msgstr "CPU カウント" #: assets/serializers/asset/host.py:18 msgid "CPU cores" -msgstr "" +msgstr "CPU カラー" #: assets/serializers/asset/host.py:19 msgid "CPU vcpus" -msgstr "" +msgstr "CPU 合計" #: assets/serializers/asset/host.py:20 msgid "Memory" -msgstr "" +msgstr "メモリ" #: assets/serializers/asset/host.py:21 msgid "Disk total" -msgstr "" +msgstr "ディスクの合計" #: assets/serializers/asset/host.py:22 msgid "Disk info" -msgstr "" +msgstr "ディスク情報" #: assets/serializers/asset/host.py:24 msgid "OS" -msgstr "" +msgstr "OS" #: assets/serializers/asset/host.py:25 msgid "OS version" -msgstr "" +msgstr "システムバージョン" #: assets/serializers/asset/host.py:26 msgid "OS arch" -msgstr "" +msgstr "システムアーキテクチャ" #: assets/serializers/asset/host.py:27 msgid "Hostname raw" -msgstr "" +msgstr "ホスト名生" #: assets/serializers/asset/host.py:28 msgid "Asset number" -msgstr "" +msgstr "資産番号" #: assets/serializers/automations/change_secret.py:28 #: xpack/plugins/change_auth_plan/models/asset.py:50 #: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "SSH Key strategy" -msgstr "" +msgstr "SSHキー戦略" #: assets/serializers/automations/change_secret.py:70 #: xpack/plugins/change_auth_plan/serializers/base.py:58 msgid "* Please enter the correct password length" -msgstr "" +msgstr "* 正しいパスワードの長さを入力してください" #: assets/serializers/automations/change_secret.py:73 #: xpack/plugins/change_auth_plan/serializers/base.py:61 msgid "* Password length range 6-30 bits" -msgstr "" +msgstr "* パスワードの長さの範囲6-30ビット" #: assets/serializers/automations/change_secret.py:117 #: assets/serializers/automations/change_secret.py:145 audits/const.py:74 @@ -1359,63 +1436,68 @@ msgstr "" #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" -msgstr "" +msgstr "成功" #: assets/serializers/automations/gather_accounts.py:23 +#, fuzzy msgid "Executed amount" -msgstr "" +msgstr "実行時間" #: assets/serializers/base.py:21 msgid "Key password" -msgstr "" +msgstr "キーパスワード" #: assets/serializers/cagegory.py:9 msgid "Constraints" msgstr "" #: assets/serializers/cagegory.py:15 +#, fuzzy msgid "Types" -msgstr "" +msgstr "タイプ" #: assets/serializers/domain.py:16 msgid "Gateway" -msgstr "" +msgstr "ゲートウェイ" #: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:9 msgid "Hostname" -msgstr "" +msgstr "ホスト名" #: assets/serializers/label.py:12 msgid "Assets amount" -msgstr "" +msgstr "資産額" #: assets/serializers/label.py:13 msgid "Category display" -msgstr "" +msgstr "カテゴリ表示" #: assets/serializers/node.py:17 msgid "value" -msgstr "" +msgstr "値" #: assets/serializers/node.py:31 msgid "Can't contains: /" -msgstr "" +msgstr "含まれない:/" #: assets/serializers/node.py:41 msgid "The same level node name cannot be the same" -msgstr "" +msgstr "同じレベルのノード名を同じにすることはできません。" #: assets/serializers/platform.py:24 +#, fuzzy msgid "SFTP enabled" -msgstr "" +msgstr "MFA有効化" #: assets/serializers/platform.py:25 +#, fuzzy msgid "SFTP home" -msgstr "" +msgstr "SFTPルート" #: assets/serializers/platform.py:28 +#, fuzzy msgid "Auto fill" -msgstr "" +msgstr "自動" #: assets/serializers/platform.py:79 msgid "Primary" @@ -1423,43 +1505,47 @@ msgstr "" #: assets/serializers/utils.py:13 msgid "Password can not contains `{{` " -msgstr "" +msgstr "パスワードには '{{' を含まない" #: assets/serializers/utils.py:16 msgid "Password can not contains `'` " -msgstr "" +msgstr "パスワードには `'` を含まない" #: assets/serializers/utils.py:18 msgid "Password can not contains `\"` " -msgstr "" +msgstr "パスワードには `\"` を含まない" #: assets/serializers/utils.py:24 msgid "private key invalid or passphrase error" -msgstr "" +msgstr "秘密鍵が無効またはpassphraseエラー" #: assets/tasks/automation.py:11 +#, fuzzy msgid "Execute automation" -msgstr "" +msgstr "バッチ実行コマンド" #: assets/tasks/backup.py:13 +#, fuzzy msgid "Execute account backup plan" -msgstr "" +msgstr "アカウントバックアップ計画" #: assets/tasks/gather_accounts.py:31 +#, fuzzy msgid "Gather assets accounts" -msgstr "" +msgstr "資産ユーザーの収集" #: assets/tasks/gather_facts.py:26 msgid "Update some assets hardware info. " -msgstr "" +msgstr "一部の資産ハードウェア情報を更新します。" #: assets/tasks/gather_facts.py:44 +#, fuzzy msgid "Manually update the hardware information of assets" -msgstr "" +msgstr "ノード資産のハードウェア情報を更新します。" #: assets/tasks/gather_facts.py:49 msgid "Update assets hardware info: " -msgstr "" +msgstr "資産のハードウェア情報を更新する:" #: assets/tasks/gather_facts.py:53 msgid "Manually update the hardware information of assets under a node" @@ -1467,7 +1553,7 @@ msgstr "" #: assets/tasks/gather_facts.py:59 msgid "Update node asset hardware information: " -msgstr "" +msgstr "ノード資産のハードウェア情報を更新します。" #: assets/tasks/nodes_amount.py:16 msgid "Check the amount of assets under the node" @@ -1477,269 +1563,278 @@ msgstr "" msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "" +"セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ" +"ん" #: assets/tasks/nodes_amount.py:34 msgid "Periodic check the amount of assets under the node" msgstr "" #: assets/tasks/ping.py:21 assets/tasks/ping.py:39 +#, fuzzy msgid "Test assets connectivity " -msgstr "" +msgstr "資産の接続性をテストします。" #: assets/tasks/ping.py:33 +#, fuzzy msgid "Manually test the connectivity of a asset" -msgstr "" +msgstr "資産接続をテストできます" #: assets/tasks/ping.py:43 msgid "Manually test the connectivity of assets under a node" msgstr "" #: assets/tasks/ping.py:49 +#, fuzzy msgid "Test if the assets under the node are connectable " -msgstr "" +msgstr "ノードの下のアセットが接続可能かどうかをテストします。" #: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 +#, fuzzy msgid "Push accounts to assets" -msgstr "" +msgstr "システムユーザーを資産にプッシュする:" #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" -msgstr "" +msgstr "資産が無効化されました。スキップ: {}" #: assets/tasks/utils.py:21 msgid "Asset may not be support ansible, skipped: {}" -msgstr "" +msgstr "資産はサポートできない場合があります。スキップ: {}" #: assets/tasks/utils.py:39 msgid "For security, do not push user {}" -msgstr "" +msgstr "セキュリティのために、ユーザー {} をプッシュしないでください" #: assets/tasks/utils.py:55 msgid "No assets matched, stop task" -msgstr "" +msgstr "一致する資産がない、タスクを停止" #: assets/tasks/verify_account.py:30 msgid "Verify asset account availability" msgstr "" #: assets/tasks/verify_account.py:37 +#, fuzzy msgid "Verify accounts connectivity" -msgstr "" +msgstr "テストアカウント接続:" #: audits/apps.py:9 msgid "Audits" -msgstr "" +msgstr "監査" #: audits/backends/db.py:12 msgid "The text content is too long. Use Elasticsearch to store operation logs" -msgstr "" +msgstr "文章の内容が長すぎる。Elasticsearchで操作履歴を保存する" #: audits/backends/db.py:24 audits/backends/db.py:26 msgid "Tips" -msgstr "" +msgstr "謎々" #: audits/const.py:45 msgid "Mkdir" -msgstr "" +msgstr "Mkdir" #: audits/const.py:46 msgid "Rmdir" -msgstr "" +msgstr "Rmdir" #: audits/const.py:47 audits/const.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:226 msgid "Delete" -msgstr "" +msgstr "削除" #: audits/const.py:48 perms/const.py:13 msgid "Upload" -msgstr "" +msgstr "アップロード" #: audits/const.py:49 msgid "Rename" -msgstr "" +msgstr "名前の変更" #: audits/const.py:50 msgid "Symlink" -msgstr "" +msgstr "Symlink" #: audits/const.py:51 perms/const.py:14 msgid "Download" -msgstr "" +msgstr "ダウンロード" #: audits/const.py:55 rbac/tree.py:224 msgid "View" -msgstr "" +msgstr "表示" #: audits/const.py:56 rbac/tree.py:225 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" -msgstr "" +msgstr "更新" #: audits/const.py:58 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:223 msgid "Create" -msgstr "" +msgstr "作成" #: audits/const.py:63 settings/serializers/terminal.py:6 #: terminal/models/applet/host.py:24 terminal/models/component/terminal.py:159 msgid "Terminal" -msgstr "" +msgstr "ターミナル" #: audits/const.py:70 msgid "-" -msgstr "" +msgstr "-" #: audits/handler.py:134 msgid "Yes" -msgstr "" +msgstr "是" #: audits/handler.py:134 msgid "No" -msgstr "" +msgstr "否" #: audits/models.py:32 audits/models.py:55 audits/models.py:96 #: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 msgid "Remote addr" -msgstr "" +msgstr "リモートaddr" #: audits/models.py:37 audits/serializers.py:19 msgid "Operate" -msgstr "" +msgstr "操作" #: audits/models.py:39 msgid "Filename" -msgstr "" +msgstr "ファイル名" #: audits/models.py:44 msgid "File transfer log" -msgstr "" +msgstr "ファイル転送ログ" #: audits/models.py:53 audits/serializers.py:91 msgid "Resource Type" -msgstr "" +msgstr "リソースタイプ" #: audits/models.py:54 msgid "Resource" -msgstr "" +msgstr "リソース" #: audits/models.py:56 audits/models.py:98 #: terminal/backends/command/serializers.py:41 msgid "Datetime" -msgstr "" +msgstr "時間" #: audits/models.py:88 msgid "Operate log" -msgstr "" +msgstr "ログの操作" #: audits/models.py:94 msgid "Change by" -msgstr "" +msgstr "による変更" #: audits/models.py:104 msgid "Password change log" -msgstr "" +msgstr "パスワード変更ログ" #: audits/models.py:111 msgid "Login type" -msgstr "" +msgstr "ログインタイプ" #: audits/models.py:113 tickets/models/ticket/login_confirm.py:10 msgid "Login ip" -msgstr "" +msgstr "ログインIP" #: audits/models.py:115 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/models/ticket/login_confirm.py:11 msgid "Login city" -msgstr "" +msgstr "ログイン都市" #: audits/models.py:118 audits/serializers.py:62 msgid "User agent" -msgstr "" +msgstr "ユーザーエージェント" #: audits/models.py:121 audits/serializers.py:39 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:698 #: users/serializers/profile.py:126 msgid "MFA" -msgstr "" +msgstr "MFA" #: audits/models.py:131 msgid "Date login" -msgstr "" +msgstr "日付ログイン" #: audits/models.py:133 audits/serializers.py:64 msgid "Authentication backend" -msgstr "" +msgstr "認証バックエンド" #: audits/models.py:174 msgid "User login log" -msgstr "" +msgstr "ユーザーログインログ" #: audits/serializers.py:63 msgid "Reason display" -msgstr "" +msgstr "理由表示" #: audits/signal_handlers.py:48 msgid "SSH Key" -msgstr "" +msgstr "SSHキー" #: audits/signal_handlers.py:50 settings/serializers/auth/sso.py:10 msgid "SSO" -msgstr "" +msgstr "SSO" #: audits/signal_handlers.py:51 msgid "Auth Token" -msgstr "" +msgstr "認証トークン" #: audits/signal_handlers.py:52 authentication/notifications.py:73 #: authentication/views/login.py:73 authentication/views/wecom.py:178 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 #: users/models/user.py:736 msgid "WeCom" -msgstr "" +msgstr "企業微信" #: audits/signal_handlers.py:53 authentication/views/feishu.py:145 #: authentication/views/login.py:85 notifications/backends/__init__.py:14 #: settings/serializers/auth/feishu.py:10 users/models/user.py:738 msgid "FeiShu" -msgstr "" +msgstr "本を飛ばす" #: audits/signal_handlers.py:54 authentication/views/dingtalk.py:180 #: authentication/views/login.py:79 notifications/backends/__init__.py:12 #: settings/serializers/auth/dingtalk.py:10 users/models/user.py:737 msgid "DingTalk" -msgstr "" +msgstr "DingTalk" #: audits/signal_handlers.py:55 authentication/models/temp_token.py:16 msgid "Temporary token" -msgstr "" +msgstr "仮パスワード" #: authentication/api/confirm.py:40 msgid "This action require verify your MFA" -msgstr "" +msgstr "この操作には、MFAを検証する必要があります" #: authentication/api/mfa.py:59 msgid "Current user not support mfa type: {}" -msgstr "" +msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" #: authentication/api/password.py:31 terminal/api/session/session.py:225 #: users/views/profile/reset.py:44 msgid "User does not exist: {}" -msgstr "" +msgstr "ユーザーが存在しない: {}" #: authentication/api/password.py:31 users/views/profile/reset.py:127 msgid "No user matched" -msgstr "" +msgstr "ユーザーにマッチしなかった" #: authentication/api/password.py:35 msgid "" "The user is from {}, please go to the corresponding system to change the " "password" msgstr "" +"ユーザーは {}からです。対応するシステムにアクセスしてパスワードを変更してくだ" +"さい。" #: authentication/api/password.py:59 #: authentication/templates/authentication/login.html:256 @@ -1748,7 +1843,7 @@ msgstr "" #: users/templates/users/forgot_password_previewing.html:13 #: users/templates/users/forgot_password_previewing.html:14 msgid "Forgot password" -msgstr "" +msgstr "パスワードを忘れた" #: authentication/apps.py:7 settings/serializers/auth/base.py:10 #: settings/serializers/auth/cas.py:10 settings/serializers/auth/dingtalk.py:10 @@ -1757,123 +1852,125 @@ msgstr "" #: settings/serializers/auth/radius.py:13 settings/serializers/auth/saml2.py:11 #: settings/serializers/auth/sso.py:10 settings/serializers/auth/wecom.py:10 msgid "Authentication" -msgstr "" +msgstr "認証" #: authentication/backends/custom.py:58 #: authentication/backends/oauth2/backends.py:158 msgid "User invalid, disabled or expired" -msgstr "" +msgstr "ユーザーが無効、無効、または期限切れです" #: authentication/backends/drf.py:56 msgid "Invalid signature header. No credentials provided." -msgstr "" +msgstr "署名ヘッダーが無効です。資格情報は提供されていません。" #: authentication/backends/drf.py:59 msgid "Invalid signature header. Signature string should not contain spaces." -msgstr "" +msgstr "署名ヘッダーが無効です。署名文字列にはスペースを含まないでください。" #: authentication/backends/drf.py:66 msgid "Invalid signature header. Format like AccessKeyId:Signature" -msgstr "" +msgstr "署名ヘッダーが無効です。AccessKeyIdのような形式: Signature" #: authentication/backends/drf.py:70 msgid "" "Invalid signature header. Signature string should not contain invalid " "characters." msgstr "" +"署名ヘッダーが無効です。署名文字列に無効な文字を含めることはできません。" #: authentication/backends/drf.py:90 authentication/backends/drf.py:106 msgid "Invalid signature." -msgstr "" +msgstr "署名が無効です。" #: authentication/backends/drf.py:97 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" -msgstr "" +msgstr "HTTP header: Date not provide or not" #: authentication/backends/drf.py:102 msgid "Expired, more than 15 minutes" -msgstr "" +msgstr "期限切れ、15分以上" #: authentication/backends/drf.py:109 msgid "User disabled." -msgstr "" +msgstr "ユーザーが無効になりました。" #: authentication/backends/drf.py:127 msgid "Invalid token header. No credentials provided." -msgstr "" +msgstr "無効なトークンヘッダー。資格情報は提供されていません。" #: authentication/backends/drf.py:130 msgid "Invalid token header. Sign string should not contain spaces." -msgstr "" +msgstr "無効なトークンヘッダー。記号文字列にはスペースを含めないでください。" #: authentication/backends/drf.py:137 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" +"無効なトークンヘッダー。署名文字列に無効な文字を含めることはできません。" #: authentication/backends/drf.py:148 msgid "Invalid token or cache refreshed." -msgstr "" +msgstr "無効なトークンまたはキャッシュの更新。" #: authentication/confirm/password.py:16 msgid "Authentication failed password incorrect" -msgstr "" +msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません)" #: authentication/confirm/relogin.py:10 msgid "Login time has exceeded {} minutes, please login again" -msgstr "" +msgstr "ログイン時間が {} 分を超えました。もう一度ログインしてください" #: authentication/errors/const.py:18 msgid "Username/password check failed" -msgstr "" +msgstr "ユーザー名/パスワードのチェックに失敗しました" #: authentication/errors/const.py:19 msgid "Password decrypt failed" -msgstr "" +msgstr "パスワードの復号化に失敗しました" #: authentication/errors/const.py:20 msgid "MFA failed" -msgstr "" +msgstr "MFAに失敗しました" #: authentication/errors/const.py:21 msgid "MFA unset" -msgstr "" +msgstr "MFAセットなし" #: authentication/errors/const.py:22 msgid "Username does not exist" -msgstr "" +msgstr "ユーザー名が存在しません" #: authentication/errors/const.py:23 msgid "Password expired" -msgstr "" +msgstr "パスワード期限切れ" #: authentication/errors/const.py:24 msgid "Disabled or expired" -msgstr "" +msgstr "無効または期限切れ" #: authentication/errors/const.py:25 msgid "This account is inactive." -msgstr "" +msgstr "このアカウントは非アクティブです。" #: authentication/errors/const.py:26 msgid "This account is expired" -msgstr "" +msgstr "このアカウントは期限切れです" #: authentication/errors/const.py:27 msgid "Auth backend not match" -msgstr "" +msgstr "Authバックエンドが一致しない" #: authentication/errors/const.py:28 msgid "ACL is not allowed" -msgstr "" +msgstr "ログイン アクセス制御は許可されません" #: authentication/errors/const.py:29 msgid "Only local users are allowed" -msgstr "" +msgstr "ローカルユーザーのみが許可されています" #: authentication/errors/const.py:39 msgid "No session found, check your cookie" -msgstr "" +msgstr "セッションが見つかりませんでした。クッキーを確認してください" #: authentication/errors/const.py:41 #, python-brace-format @@ -1882,18 +1979,26 @@ msgid "" "You can also try {times_try} times (The account will be temporarily locked " "for {block_time} minutes)" msgstr "" +"入力したユーザー名またはパスワードが正しくありません。再度入力してください。 " +"{times_try} 回試すこともできます (アカウントは {block_time} 分の間一時的に" +"ロックされます)" #: authentication/errors/const.py:47 authentication/errors/const.py:55 msgid "" "The account has been locked (please contact admin to unlock it or try again " "after {} minutes)" msgstr "" +"アカウントがロックされています (管理者に連絡してロックを解除するか、 {} 分後" +"にもう一度お試しください)" #: authentication/errors/const.py:51 +#, fuzzy msgid "" "The address has been locked (please contact admin to unlock it or try again " "after {} minutes)" msgstr "" +"IPがロックされています (管理者に連絡してロックを解除するか、 {} 分後にもう一" +"度お試しください)" #: authentication/errors/const.py:59 #, python-brace-format @@ -1901,153 +2006,157 @@ msgid "" "{error}, You can also try {times_try} times (The account will be temporarily " "locked for {block_time} minutes)" msgstr "" +"{error},{times_try} 回も試すことができます (アカウントは {block_time} 分の間" +"一時的にロックされます)" #: authentication/errors/const.py:63 msgid "MFA required" -msgstr "" +msgstr "MFAが必要" #: authentication/errors/const.py:64 msgid "MFA not set, please set it first" -msgstr "" +msgstr "MFAをセットしない、最初にセットしてください" #: authentication/errors/const.py:65 msgid "Login confirm required" -msgstr "" +msgstr "ログイン確認が必要" #: authentication/errors/const.py:66 msgid "Wait login confirm ticket for accept" -msgstr "" +msgstr "受け入れのためのログイン確認チケットを待つ" #: authentication/errors/const.py:67 msgid "Login confirm ticket was {}" -msgstr "" +msgstr "ログイン確認チケットは {} でした" #: authentication/errors/failed.py:146 msgid "Current IP and Time period is not allowed" -msgstr "" +msgstr "現在の IP と期間はログインを許可されていません" #: authentication/errors/failed.py:151 msgid "Please enter MFA code" -msgstr "" +msgstr "MFAコードを入力してください" #: authentication/errors/failed.py:156 msgid "Please enter SMS code" -msgstr "" +msgstr "SMSコードを入力してください" #: authentication/errors/failed.py:161 users/exceptions.py:15 msgid "Phone not set" -msgstr "" +msgstr "電話が設定されていない" #: authentication/errors/mfa.py:8 msgid "SSO auth closed" -msgstr "" +msgstr "SSO authは閉鎖されました" #: authentication/errors/mfa.py:18 authentication/views/wecom.py:80 msgid "WeCom is already bound" -msgstr "" +msgstr "企業の微信はすでにバインドされています" #: authentication/errors/mfa.py:23 authentication/views/wecom.py:237 #: authentication/views/wecom.py:291 msgid "WeCom is not bound" -msgstr "" +msgstr "企業の微信をバインドしていません" #: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 #: authentication/views/dingtalk.py:297 msgid "DingTalk is not bound" -msgstr "" +msgstr "DingTalkはバインドされていません" #: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 msgid "FeiShu is not bound" -msgstr "" +msgstr "本を飛ばすは拘束されていません" #: authentication/errors/mfa.py:38 msgid "Your password is invalid" -msgstr "" +msgstr "パスワードが無効です" #: authentication/errors/redirect.py:85 authentication/mixins.py:306 msgid "Your password is too simple, please change it for security" -msgstr "" +msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください" #: authentication/errors/redirect.py:93 authentication/mixins.py:313 msgid "You should to change your password before login" -msgstr "" +msgstr "ログインする前にパスワードを変更する必要があります" #: authentication/errors/redirect.py:101 authentication/mixins.py:320 msgid "Your password has expired, please reset before logging in" msgstr "" +"パスワードの有効期限が切れました。ログインする前にリセットしてください。" #: authentication/forms.py:45 msgid "{} days auto login" -msgstr "" +msgstr "{} 日自動ログイン" #: authentication/forms.py:56 msgid "MFA Code" -msgstr "" +msgstr "MFAコード" #: authentication/forms.py:57 msgid "MFA type" -msgstr "" +msgstr "MFAタイプ" #: authentication/forms.py:65 #: authentication/templates/authentication/_captcha_field.html:15 msgid "Captcha" -msgstr "" +msgstr "キャプチャ" #: authentication/forms.py:70 users/forms/profile.py:28 msgid "MFA code" -msgstr "" +msgstr "MFAコード" #: authentication/forms.py:72 msgid "Dynamic code" -msgstr "" +msgstr "動的コード" #: authentication/mfa/base.py:7 msgid "Please input security code" -msgstr "" +msgstr "セキュリティコードを入力してください" #: authentication/mfa/custom.py:20 msgid "MFA Custom code invalid" -msgstr "" +msgstr "カスタム MFA 検証コードの検証に失敗しました" #: authentication/mfa/custom.py:26 msgid "MFA custom verification code" -msgstr "" +msgstr "カスタム MFA 検証コード" #: authentication/mfa/custom.py:56 msgid "MFA custom global enabled, cannot disable" msgstr "" +"カスタム MFA はグローバルに有効になっており、無効にすることはできません" #: authentication/mfa/otp.py:7 msgid "OTP code invalid, or server time error" -msgstr "" +msgstr "OTPコードが無効、またはサーバー時間エラー" #: authentication/mfa/otp.py:12 msgid "OTP" -msgstr "" +msgstr "OTP" #: authentication/mfa/otp.py:13 msgid "OTP verification code" -msgstr "" +msgstr "OTP検証コード" #: authentication/mfa/otp.py:48 msgid "Virtual OTP based MFA" -msgstr "" +msgstr "仮想OTPベースのMFA" #: authentication/mfa/radius.py:7 msgid "Radius verify code invalid" -msgstr "" +msgstr "Radius verifyコードが無効" #: authentication/mfa/radius.py:13 msgid "Radius verification code" -msgstr "" +msgstr "半径確認コード" #: authentication/mfa/radius.py:44 msgid "Radius global enabled, cannot disable" -msgstr "" +msgstr "Radius globalが有効になり、無効にできません" #: authentication/mfa/sms.py:7 msgid "SMS verify code invalid" -msgstr "" +msgstr "メッセージ検証コードが無効" #: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 @@ -2055,122 +2164,127 @@ msgstr "" #: users/forms/profile.py:106 users/templates/users/forgot_password.html:111 #: users/views/profile/reset.py:79 msgid "SMS" -msgstr "" +msgstr "メッセージ" #: authentication/mfa/sms.py:13 msgid "SMS verification code" -msgstr "" +msgstr "SMS確認コード" #: authentication/mfa/sms.py:57 msgid "Set phone number to enable" -msgstr "" +msgstr "電話番号を設定して有効にする" #: authentication/mfa/sms.py:61 msgid "Clear phone number to disable" -msgstr "" +msgstr "無効にする電話番号をクリアする" #: authentication/middleware.py:77 settings/utils/ldap.py:652 msgid "Authentication failed (before login check failed): {}" -msgstr "" +msgstr "認証に失敗しました (ログインチェックが失敗する前): {}" #: authentication/mixins.py:256 msgid "The MFA type ({}) is not enabled" -msgstr "" +msgstr "MFAタイプ ({}) が有効になっていない" #: authentication/mixins.py:296 msgid "Please change your password" -msgstr "" +msgstr "パスワードを変更してください" #: authentication/models/connection_token.py:31 #: terminal/serializers/storage.py:111 msgid "Account name" -msgstr "" +msgstr "アカウント名" #: authentication/models/connection_token.py:32 +#, fuzzy msgid "Input username" -msgstr "" +msgstr "カスタムユーザー名" #: authentication/models/connection_token.py:33 +#, fuzzy msgid "Input secret" -msgstr "" +msgstr "クライアント秘密" #: authentication/models/connection_token.py:35 #: authentication/serializers/connect_token_secret.py:110 #: perms/models/perm_token.py:17 +#, fuzzy msgid "Connect method" -msgstr "" +msgstr "接続タイムアウト" #: authentication/models/connection_token.py:36 #: rbac/serializers/rolebinding.py:21 msgid "User display" -msgstr "" +msgstr "ユーザー表示" #: authentication/models/connection_token.py:37 msgid "Asset display" -msgstr "" +msgstr "アセット名" #: authentication/models/connection_token.py:38 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 #: tickets/models/ticket/apply_application.py:31 #: tickets/models/ticket/apply_asset.py:20 users/models/user.py:719 msgid "Date expired" -msgstr "" +msgstr "期限切れの日付" #: authentication/models/connection_token.py:42 msgid "Connection token" -msgstr "" +msgstr "接続トークン" #: authentication/models/connection_token.py:44 msgid "Can view connection token secret" -msgstr "" +msgstr "接続トークンの秘密を表示できます" #: authentication/models/connection_token.py:91 msgid "Connection token expired at: {}" -msgstr "" +msgstr "接続トークンの有効期限: {}" #: authentication/models/connection_token.py:94 msgid "No user or invalid user" msgstr "" #: authentication/models/connection_token.py:98 +#, fuzzy msgid "No asset or inactive asset" -msgstr "" +msgstr "アセットがアクティブ化されていません" #: authentication/models/connection_token.py:173 msgid "Super connection token" -msgstr "" +msgstr "スーパー接続トークン" #: authentication/models/private_token.py:9 msgid "Private Token" -msgstr "" +msgstr "プライベートトークン" #: authentication/models/sso_token.py:15 msgid "Expired" -msgstr "" +msgstr "期限切れ" #: authentication/models/sso_token.py:20 msgid "SSO token" -msgstr "" +msgstr "SSO token" #: authentication/models/temp_token.py:11 msgid "Verified" -msgstr "" +msgstr "確認済み" #: authentication/notifications.py:19 msgid "Different city login reminder" -msgstr "" +msgstr "異なる都市ログインのリマインダー" #: authentication/notifications.py:52 msgid "binding reminder" -msgstr "" +msgstr "バインディングリマインダー" #: authentication/serializers/connect_token_secret.py:109 +#, fuzzy msgid "Expired now" -msgstr "" +msgstr "期限切れ" #: authentication/serializers/connection_token.py:14 msgid "Expired time" -msgstr "" +msgstr "期限切れ時間" #: authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 @@ -2180,90 +2294,90 @@ msgstr "" #: users/templates/users/forgot_password.html:116 #: users/views/profile/reset.py:73 msgid "Email" -msgstr "" +msgstr "メール" #: authentication/serializers/password_mfa.py:29 #: users/templates/users/forgot_password.html:107 msgid "The {} cannot be empty" -msgstr "" +msgstr "{} 空にしてはならない" #: authentication/serializers/token.py:79 perms/serializers/permission.py:30 #: perms/serializers/permission.py:61 users/serializers/user.py:203 msgid "Is valid" -msgstr "" +msgstr "有効です" #: authentication/templates/authentication/_access_key_modal.html:6 msgid "API key list" -msgstr "" +msgstr "APIキーリスト" #: authentication/templates/authentication/_access_key_modal.html:18 msgid "Using api key sign api header, every requests header difference" -msgstr "" +msgstr "APIキー記号APIヘッダーを使用すると、すべてのリクエストヘッダーの違い" #: authentication/templates/authentication/_access_key_modal.html:19 msgid "docs" -msgstr "" +msgstr "ドキュメント" #: authentication/templates/authentication/_access_key_modal.html:30 #: users/serializers/group.py:35 msgid "ID" -msgstr "" +msgstr "ID" #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:96 terminal/notifications.py:144 msgid "Date" -msgstr "" +msgstr "日付" #: authentication/templates/authentication/_access_key_modal.html:48 msgid "Show" -msgstr "" +msgstr "表示" #: authentication/templates/authentication/_access_key_modal.html:66 #: settings/serializers/security.py:39 users/models/user.py:559 #: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" -msgstr "" +msgstr "無効化" #: authentication/templates/authentication/_access_key_modal.html:67 #: users/models/user.py:560 users/serializers/profile.py:117 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" -msgstr "" +msgstr "有効化" #: authentication/templates/authentication/_access_key_modal.html:147 msgid "Delete success" -msgstr "" +msgstr "削除成功" #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 #: templates/_modal.html:22 tickets/const.py:44 msgid "Close" -msgstr "" +msgstr "閉じる" #: authentication/templates/authentication/_captcha_field.html:8 msgid "Play CAPTCHA as audio file" -msgstr "" +msgstr "CAPTCHAをオーディオファイルとして再生する" #: authentication/templates/authentication/_mfa_confirm_modal.html:5 msgid "MFA confirm" -msgstr "" +msgstr "MFA確認" #: authentication/templates/authentication/_mfa_confirm_modal.html:17 msgid "Need MFA for view auth" -msgstr "" +msgstr "ビューオートのためにMFAが必要" #: authentication/templates/authentication/_mfa_confirm_modal.html:20 #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:37 #: templates/_modal.html:23 templates/flash_message_standalone.html:37 #: users/templates/users/user_password_verify.html:20 msgid "Confirm" -msgstr "" +msgstr "確認" #: authentication/templates/authentication/_mfa_confirm_modal.html:25 msgid "Code error" -msgstr "" +msgstr "コードエラー" #: authentication/templates/authentication/_msg_different_city.html:3 #: authentication/templates/authentication/_msg_oauth_bind.html:3 @@ -2280,116 +2394,125 @@ msgstr "" #: users/templates/users/_msg_reset_mfa.html:4 #: users/templates/users/_msg_reset_ssh_key.html:4 msgid "Hello" -msgstr "" +msgstr "こんにちは" #: authentication/templates/authentication/_msg_different_city.html:6 msgid "Your account has remote login behavior, please pay attention" -msgstr "" +msgstr "アカウントにリモートログイン動作があります。注意してください" #: authentication/templates/authentication/_msg_different_city.html:10 msgid "Login time" -msgstr "" +msgstr "ログイン時間" #: authentication/templates/authentication/_msg_different_city.html:16 msgid "" "If you suspect that the login behavior is abnormal, please modify the " "account password in time." msgstr "" +"ログイン動作が異常であると疑われる場合は、時間内にアカウントのパスワードを変" +"更してください。" #: authentication/templates/authentication/_msg_oauth_bind.html:6 msgid "Your account has just been bound to" -msgstr "" +msgstr "アカウントはにバインドされています" #: authentication/templates/authentication/_msg_oauth_bind.html:17 msgid "If the operation is not your own, unbind and change the password." -msgstr "" +msgstr "操作が独自のものでない場合は、パスワードをバインド解除して変更します。" #: authentication/templates/authentication/_msg_reset_password.html:6 msgid "" "Please click the link below to reset your password, if not your request, " "concern your account security" msgstr "" +"下のリンクをクリックしてパスワードをリセットしてください。リクエストがない場" +"合は、アカウントのセキュリティに関係します。" #: authentication/templates/authentication/_msg_reset_password.html:10 msgid "Click here reset password" -msgstr "" +msgstr "ここをクリックしてパスワードをリセット" #: authentication/templates/authentication/_msg_reset_password.html:16 #: users/templates/users/_msg_user_created.html:22 msgid "This link is valid for 1 hour. After it expires" -msgstr "" +msgstr "このリンクは1時間有効です。有効期限が切れた後" #: authentication/templates/authentication/_msg_reset_password.html:17 #: users/templates/users/_msg_user_created.html:23 msgid "request new one" -msgstr "" +msgstr "新しいものを要求する" #: authentication/templates/authentication/_msg_reset_password_code.html:12 #: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 #: users/forms/profile.py:104 users/templates/users/forgot_password.html:65 msgid "Verify code" -msgstr "" +msgstr "コードの確認" #: authentication/templates/authentication/_msg_reset_password_code.html:15 msgid "" "Copy the verification code to the Reset Password page to reset the password." -msgstr "" +msgstr "パスワードリセットページにcaptchaをコピーし、パスワードをリセットする" #: authentication/templates/authentication/_msg_reset_password_code.html:18 msgid "The validity period of the verification code is one minute" -msgstr "" +msgstr "認証コードの有効時間は 1 分" #: authentication/templates/authentication/_msg_rest_password_success.html:5 msgid "Your password has just been successfully updated" -msgstr "" +msgstr "パスワードが正常に更新されました" #: authentication/templates/authentication/_msg_rest_password_success.html:9 #: authentication/templates/authentication/_msg_rest_public_key_success.html:9 msgid "Browser" -msgstr "" +msgstr "ブラウザ" #: authentication/templates/authentication/_msg_rest_password_success.html:13 msgid "" "If the password update was not initiated by you, your account may have " "security issues" msgstr "" +"パスワードの更新が開始されなかった場合、アカウントにセキュリティ上の問題があ" +"る可能性があります" #: authentication/templates/authentication/_msg_rest_password_success.html:14 #: authentication/templates/authentication/_msg_rest_public_key_success.html:14 msgid "If you have any questions, you can contact the administrator" -msgstr "" +msgstr "質問があれば、管理者に連絡できます" #: authentication/templates/authentication/_msg_rest_public_key_success.html:5 msgid "Your public key has just been successfully updated" -msgstr "" +msgstr "公開鍵が正常に更新されました" #: authentication/templates/authentication/_msg_rest_public_key_success.html:13 msgid "" "If the public key update was not initiated by you, your account may have " "security issues" msgstr "" +"公開鍵の更新が開始されなかった場合、アカウントにセキュリティ上の問題がある可" +"能性があります" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 #: templates/flash_message_standalone.html:28 tickets/const.py:17 msgid "Cancel" -msgstr "" +msgstr "キャンセル" #: authentication/templates/authentication/login.html:221 msgid "Welcome back, please enter username and password to login" msgstr "" +"おかえりなさい、ログインするためにユーザー名とパスワードを入力してください" #: authentication/templates/authentication/login.html:264 #: templates/_header_bar.html:89 msgid "Login" -msgstr "" +msgstr "ログイン" #: authentication/templates/authentication/login.html:271 msgid "More login options" -msgstr "" +msgstr "その他のログインオプション" #: authentication/templates/authentication/login_mfa.html:6 msgid "MFA Auth" -msgstr "" +msgstr "MFA マルチファクタ認証" #: authentication/templates/authentication/login_mfa.html:19 #: users/templates/users/user_otp_check_password.html:12 @@ -2397,234 +2520,237 @@ msgstr "" #: users/templates/users/user_otp_enable_install_app.html:29 #: users/templates/users/user_verify_mfa.html:30 msgid "Next" -msgstr "" +msgstr "次へ" #: authentication/templates/authentication/login_mfa.html:22 msgid "Can't provide security? Please contact the administrator!" -msgstr "" +msgstr "セキュリティを提供できませんか? 管理者に連絡してください!" #: authentication/templates/authentication/login_wait_confirm.html:41 msgid "Refresh" -msgstr "" +msgstr "リフレッシュ" #: authentication/templates/authentication/login_wait_confirm.html:46 msgid "Copy link" -msgstr "" +msgstr "リンクのコピー" #: authentication/templates/authentication/login_wait_confirm.html:51 msgid "Return" -msgstr "" +msgstr "返品" #: authentication/templates/authentication/login_wait_confirm.html:116 msgid "Copy success" -msgstr "" +msgstr "コピー成功" #: authentication/utils.py:28 common/utils/ip/geoip/utils.py:24 #: xpack/plugins/cloud/const.py:27 msgid "LAN" -msgstr "" +msgstr "ローカルエリアネットワーク" #: authentication/views/dingtalk.py:42 msgid "DingTalk Error, Please contact your system administrator" -msgstr "" +msgstr "DingTalkエラー、システム管理者に連絡してください" #: authentication/views/dingtalk.py:45 msgid "DingTalk Error" -msgstr "" +msgstr "DingTalkエラー" #: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 #: authentication/views/wecom.py:56 msgid "" "The system configuration is incorrect. Please contact your administrator" -msgstr "" +msgstr "システム設定が正しくありません。管理者に連絡してください" #: authentication/views/dingtalk.py:81 msgid "DingTalk is already bound" -msgstr "" +msgstr "DingTalkはすでにバインドされています" #: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 msgid "Invalid user_id" -msgstr "" +msgstr "無効なuser_id" #: authentication/views/dingtalk.py:165 msgid "DingTalk query user failed" -msgstr "" +msgstr "DingTalkクエリユーザーが失敗しました" #: authentication/views/dingtalk.py:174 msgid "The DingTalk is already bound to another user" -msgstr "" +msgstr "DingTalkはすでに別のユーザーにバインドされています" #: authentication/views/dingtalk.py:181 msgid "Binding DingTalk successfully" -msgstr "" +msgstr "DingTalkのバインドに成功" #: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 msgid "Failed to get user from DingTalk" -msgstr "" +msgstr "DingTalkからユーザーを取得できませんでした" #: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 msgid "Please login with a password and then bind the DingTalk" -msgstr "" +msgstr "パスワードでログインし、DingTalkをバインドしてください" #: authentication/views/feishu.py:40 msgid "FeiShu Error" -msgstr "" +msgstr "FeiShuエラー" #: authentication/views/feishu.py:88 msgid "FeiShu is already bound" -msgstr "" +msgstr "FeiShuはすでにバインドされています" #: authentication/views/feishu.py:130 msgid "FeiShu query user failed" -msgstr "" +msgstr "FeiShuクエリユーザーが失敗しました" #: authentication/views/feishu.py:139 msgid "The FeiShu is already bound to another user" -msgstr "" +msgstr "FeiShuはすでに別のユーザーにバインドされています" #: authentication/views/feishu.py:146 msgid "Binding FeiShu successfully" -msgstr "" +msgstr "本を飛ばすのバインドに成功" #: authentication/views/feishu.py:198 msgid "Failed to get user from FeiShu" -msgstr "" +msgstr "本を飛ばすからユーザーを取得できませんでした" #: authentication/views/feishu.py:205 msgid "Please login with a password and then bind the FeiShu" -msgstr "" +msgstr "パスワードでログインしてから本を飛ばすをバインドしてください" #: authentication/views/login.py:181 msgid "Redirecting" -msgstr "" +msgstr "リダイレクト" #: authentication/views/login.py:182 msgid "Redirecting to {} authentication" -msgstr "" +msgstr "{} 認証へのリダイレクト" #: authentication/views/login.py:205 msgid "Please enable cookies and try again." -msgstr "" +msgstr "クッキーを有効にして、もう一度お試しください。" #: authentication/views/login.py:307 msgid "" "Wait for {} confirm, You also can copy link to her/him
\n" " Don't close this page" msgstr "" +"{} 確認を待ちます。彼女/彼へのリンクをコピーすることもできます
\n" +" このページを閉じないでください" #: authentication/views/login.py:312 msgid "No ticket found" -msgstr "" +msgstr "チケットが見つかりません" #: authentication/views/login.py:348 msgid "Logout success" -msgstr "" +msgstr "ログアウト成功" #: authentication/views/login.py:349 msgid "Logout success, return login page" -msgstr "" +msgstr "ログアウト成功、ログインページを返す" #: authentication/views/wecom.py:41 msgid "WeCom Error, Please contact your system administrator" -msgstr "" +msgstr "企業微信エラー、システム管理者に連絡してください" #: authentication/views/wecom.py:44 msgid "WeCom Error" -msgstr "" +msgstr "企業微信エラー" #: authentication/views/wecom.py:163 msgid "WeCom query user failed" -msgstr "" +msgstr "企業微信ユーザーの問合せに失敗しました" #: authentication/views/wecom.py:172 msgid "The WeCom is already bound to another user" -msgstr "" +msgstr "この企業の微信はすでに他のユーザーをバインドしている。" #: authentication/views/wecom.py:179 msgid "Binding WeCom successfully" -msgstr "" +msgstr "企業の微信のバインドに成功" #: authentication/views/wecom.py:231 authentication/views/wecom.py:285 msgid "Failed to get user from WeCom" -msgstr "" +msgstr "企業の微信からユーザーを取得できませんでした" #: authentication/views/wecom.py:238 authentication/views/wecom.py:292 msgid "Please login with a password and then bind the WeCom" -msgstr "" +msgstr "パスワードでログインしてからWeComをバインドしてください" #: common/const/__init__.py:6 #, python-format msgid "%(name)s was created successfully" -msgstr "" +msgstr "%(name)s が正常に作成されました" #: common/const/__init__.py:7 #, python-format msgid "%(name)s was updated successfully" -msgstr "" +msgstr "%(name)s は正常に更新されました" #: common/const/choices.py:10 msgid "Manual trigger" -msgstr "" +msgstr "手動トリガー" #: common/const/choices.py:11 msgid "Timing trigger" -msgstr "" +msgstr "タイミングトリガー" #: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 msgid "Ready" -msgstr "" +msgstr "の準備を" #: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 msgid "Pending" -msgstr "" +msgstr "未定" #: common/const/choices.py:17 msgid "Running" msgstr "" #: common/const/choices.py:21 +#, fuzzy msgid "Canceled" -msgstr "" +msgstr "キャンセル" #: common/db/encoder.py:11 msgid "ugettext_lazy" -msgstr "" +msgstr "ugettext_lazy" #: common/db/fields.py:94 msgid "Marshal dict data to char field" -msgstr "" +msgstr "チャーフィールドへのマーシャルディクトデータ" #: common/db/fields.py:98 msgid "Marshal dict data to text field" -msgstr "" +msgstr "テキストフィールドへのマーシャルディクトデータ" #: common/db/fields.py:110 msgid "Marshal list data to char field" -msgstr "" +msgstr "元帥リストデータをチャーフィールドに" #: common/db/fields.py:114 msgid "Marshal list data to text field" -msgstr "" +msgstr "マーシャルリストデータをテキストフィールドに" #: common/db/fields.py:118 msgid "Marshal data to char field" -msgstr "" +msgstr "チャーフィールドへのマーシャルデータ" #: common/db/fields.py:122 msgid "Marshal data to text field" -msgstr "" +msgstr "テキストフィールドへのマーシャルデータ" #: common/db/fields.py:164 msgid "Encrypt field using Secret Key" -msgstr "" +msgstr "Secret Keyを使用したフィールドの暗号化" #: common/db/models.py:76 msgid "Updated by" -msgstr "" +msgstr "によって更新" #: common/drf/exc_handlers.py:25 msgid "Object" -msgstr "" +msgstr "オブジェクト" #: common/drf/fields.py:77 tickets/serializers/ticket/common.py:58 #: xpack/plugins/change_auth_plan/serializers/asset.py:64 @@ -2633,12 +2759,12 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:101 #: xpack/plugins/cloud/serializers/account_attrs.py:56 msgid "This field is required." -msgstr "" +msgstr "このフィールドは必須です。" #: common/drf/fields.py:78 -#, python-brace-format +#, fuzzy, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "" +msgstr "%s オブジェクトは存在しません。" #: common/drf/fields.py:79 #, python-brace-format @@ -2650,146 +2776,149 @@ msgid "Invalid data type, should be list" msgstr "" #: common/drf/fields.py:156 +#, fuzzy msgid "Invalid choice: {}" -msgstr "" +msgstr "無効なIP" #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" -msgstr "" +msgstr "ファイルの内容がオーバーフローしました (最大長 '{}' バイト)" #: common/drf/parsers/base.py:159 msgid "Parse file error: {}" -msgstr "" +msgstr "解析ファイルエラー: {}" #: common/drf/serializers/common.py:86 msgid "Children" msgstr "" #: common/drf/serializers/common.py:94 +#, fuzzy msgid "File" -msgstr "" +msgstr "ファイル名" #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." -msgstr "" +msgstr "%s オブジェクトは存在しません。" #: common/exceptions.py:25 msgid "Someone else is doing this. Please wait for complete" -msgstr "" +msgstr "他の誰かがこれをやっています。完了をお待ちください" #: common/exceptions.py:30 msgid "Your request timeout" -msgstr "" +msgstr "リクエストのタイムアウト" #: common/exceptions.py:35 msgid "M2M reverse not allowed" -msgstr "" +msgstr "M2Mリバースは許可されません" #: common/exceptions.py:41 msgid "Is referenced by other objects and cannot be deleted" -msgstr "" +msgstr "他のオブジェクトによって参照され、削除できません。" #: common/exceptions.py:48 msgid "This action require confirm current user" -msgstr "" +msgstr "このアクションでは、MFAの確認が必要です。" #: common/exceptions.py:56 msgid "Unexpect error occur" -msgstr "" +msgstr "予期しないエラーが発生します" #: common/mixins/api/action.py:52 msgid "Request file format may be wrong" -msgstr "" +msgstr "リクエストファイルの形式が間違っている可能性があります" #: common/mixins/models.py:33 msgid "is discard" -msgstr "" +msgstr "は破棄されます" #: common/mixins/models.py:34 msgid "discard time" -msgstr "" +msgstr "時間を捨てる" #: common/mixins/views.py:58 msgid "Export all" -msgstr "" +msgstr "すべてエクスポート" #: common/mixins/views.py:60 msgid "Export only selected items" -msgstr "" +msgstr "選択項目のみエクスポート" #: common/mixins/views.py:65 #, python-format msgid "Export filtered: %s" -msgstr "" +msgstr "検索のエクスポート: %s" #: common/plugins/es.py:28 msgid "Invalid elasticsearch config" -msgstr "" +msgstr "無効なElasticsearch構成" #: common/plugins/es.py:33 msgid "Not Support Elasticsearch8" -msgstr "" +msgstr "サポートされていません Elasticsearch8" #: common/sdk/im/exceptions.py:23 msgid "Network error, please contact system administrator" -msgstr "" +msgstr "ネットワークエラー、システム管理者に連絡してください" #: common/sdk/im/wecom/__init__.py:15 msgid "WeCom error, please contact system administrator" -msgstr "" +msgstr "企業微信エラー、システム管理者に連絡してください" #: common/sdk/sms/alibaba.py:56 msgid "Signature does not match" -msgstr "" +msgstr "署名が一致しない" #: common/sdk/sms/cmpp2.py:46 msgid "sp_id is 6 bits" -msgstr "" +msgstr "SP idは6ビット" #: common/sdk/sms/cmpp2.py:216 msgid "Failed to connect to the CMPP gateway server, err: {}" -msgstr "" +msgstr "接続ゲートウェイサーバエラー, 非: {}" #: common/sdk/sms/endpoint.py:16 msgid "Alibaba cloud" -msgstr "" +msgstr "アリ雲" #: common/sdk/sms/endpoint.py:17 msgid "Tencent cloud" -msgstr "" +msgstr "テンセント雲" #: common/sdk/sms/endpoint.py:18 xpack/plugins/cloud/const.py:13 msgid "Huawei Cloud" -msgstr "" +msgstr "華為雲" #: common/sdk/sms/endpoint.py:19 msgid "CMPP v2.0" -msgstr "" +msgstr "CMPP v2.0" #: common/sdk/sms/endpoint.py:30 msgid "SMS provider not support: {}" -msgstr "" +msgstr "SMSプロバイダーはサポートしていません: {}" #: common/sdk/sms/endpoint.py:51 msgid "SMS verification code signature or template invalid" -msgstr "" +msgstr "SMS検証コードの署名またはテンプレートが無効" #: common/sdk/sms/exceptions.py:8 msgid "The verification code has expired. Please resend it" -msgstr "" +msgstr "確認コードの有効期限が切れています。再送信してください" #: common/sdk/sms/exceptions.py:13 msgid "The verification code is incorrect" -msgstr "" +msgstr "確認コードが正しくありません" #: common/sdk/sms/exceptions.py:18 msgid "Please wait {} seconds before sending" -msgstr "" +msgstr "{} 秒待ってから送信してください" #: common/tasks.py:13 +#, fuzzy msgid "Send email" -msgstr "" +msgstr "ユーザーを送信" #: common/tasks.py:40 msgid "Send email attachment" @@ -2797,43 +2926,44 @@ msgstr "" #: common/utils/ip/geoip/utils.py:26 msgid "Invalid ip" -msgstr "" +msgstr "無効なIP" #: common/utils/ip/utils.py:78 +#, fuzzy msgid "Invalid address" -msgstr "" +msgstr "署名が無効です。" #: common/validators.py:14 msgid "Special char not allowed" -msgstr "" +msgstr "特別なcharは許可されていません" #: common/validators.py:32 msgid "This field must be unique." -msgstr "" +msgstr "このフィールドは一意である必要があります。" #: common/validators.py:40 msgid "Should not contains special characters" -msgstr "" +msgstr "特殊文字を含むべきではない" #: common/validators.py:46 msgid "The mobile phone number format is incorrect" -msgstr "" +msgstr "携帯電話番号の形式が正しくありません" #: jumpserver/conf.py:413 msgid "Create account successfully" -msgstr "" +msgstr "アカウントを正常に作成" #: jumpserver/conf.py:415 msgid "Your account has been created successfully" -msgstr "" +msgstr "アカウントが正常に作成されました" #: jumpserver/context_processor.py:12 msgid "JumpServer Open Source Bastion Host" -msgstr "" +msgstr "JumpServer オープンソースの要塞ホスト" #: jumpserver/views/celery_flower.py:23 msgid "

Flower service unavailable, check it

" -msgstr "" +msgstr "

フラワーサービス利用不可、チェック

" #: jumpserver/views/other.py:26 msgid "" @@ -2841,10 +2971,16 @@ msgid "" "configure nginx for url distribution, If you see this page, " "prove that you are not accessing the nginx listening port. Good luck." msgstr "" +"
Lunaは個別にデプロイされたプログラムです。Luna、kokoをデプロイする必要" +"があります。urlディストリビューションにnginxを設定します。
この" +"ページが表示されている場合は、nginxリスニングポートにアクセスしていないことを" +"証明してください。頑張ってください。" #: jumpserver/views/other.py:70 msgid "Websocket server run on port: {}, you should proxy it on nginx" msgstr "" +"Websocket サーバーはport: {}で実行されます。nginxでプロキシする必要がありま" +"す。" #: jumpserver/views/other.py:84 msgid "" @@ -2852,42 +2988,48 @@ msgid "" "configure nginx for url distribution, If you see this page, " "prove that you are not accessing the nginx listening port. Good luck." msgstr "" +"
Kokoは個別にデプロイされているプログラムです。Kokoをデプロイする必要が" +"あります。URL配布用にnginxを設定します。
このページが表示されて" +"いる場合は、nginxリスニングポートにアクセスしていないことを証明してください。" +"頑張ってください。" #: notifications/apps.py:7 msgid "Notifications" -msgstr "" +msgstr "通知" #: notifications/backends/__init__.py:13 msgid "Site message" -msgstr "" +msgstr "サイトメッセージ" #: notifications/models/notification.py:14 msgid "receive backend" -msgstr "" +msgstr "メッセージのバックエンド" #: notifications/models/notification.py:17 msgid "User message" -msgstr "" +msgstr "ユーザメッセージ" #: notifications/models/notification.py:20 msgid "{} subscription" -msgstr "" +msgstr "{} 購読" #: notifications/models/notification.py:32 msgid "System message" -msgstr "" +msgstr "システムメッセージ" #: notifications/notifications.py:46 msgid "Publish the station message" msgstr "" #: ops/ansible/inventory.py:75 +#, fuzzy msgid "No account available" -msgstr "" +msgstr "利用できないアカウント" #: ops/ansible/inventory.py:178 +#, fuzzy msgid "Ansible disabled" -msgstr "" +msgstr "ユーザーが無効になりました。" #: ops/ansible/inventory.py:194 msgid "Skip hosts below:" @@ -2895,31 +3037,33 @@ msgstr "" #: ops/api/celery.py:63 ops/api/celery.py:78 msgid "Waiting task start" -msgstr "" +msgstr "タスク開始待ち" #: ops/apps.py:9 ops/notifications.py:16 msgid "App ops" -msgstr "" +msgstr "アプリ操作" #: ops/const.py:6 msgid "Push" msgstr "" #: ops/const.py:7 +#, fuzzy msgid "Verify" -msgstr "" +msgstr "確認済み" #: ops/const.py:8 msgid "Collect" msgstr "" #: ops/const.py:9 +#, fuzzy msgid "Change password" -msgstr "" +msgstr "パスワードの変更" #: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 msgid "Custom password" -msgstr "" +msgstr "カスタムパスワード" #: ops/exception.py:6 msgid "no valid program entry found." @@ -2927,36 +3071,37 @@ msgstr "" #: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:73 msgid "Cycle perform" -msgstr "" +msgstr "サイクル実行" #: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 #: settings/serializers/auth/ldap.py:70 msgid "Regularly perform" -msgstr "" +msgstr "定期的に実行する" #: ops/mixin.py:108 msgid "Interval" -msgstr "" +msgstr "間隔" #: ops/mixin.py:118 msgid "* Please enter a valid crontab expression" -msgstr "" +msgstr "* 有効なcrontab式を入力してください" #: ops/mixin.py:125 msgid "Range {} to {}" -msgstr "" +msgstr "{} から {} までの範囲" #: ops/mixin.py:136 msgid "Require periodic or regularly perform setting" -msgstr "" +msgstr "定期的または定期的に設定を行う必要があります" #: ops/models/adhoc.py:18 ops/models/job.py:31 +#, fuzzy msgid "Powershell" -msgstr "" +msgstr "PowerShell" #: ops/models/adhoc.py:22 msgid "Pattern" -msgstr "" +msgstr "パターン" #: ops/models/adhoc.py:24 ops/models/job.py:38 msgid "Module" @@ -2965,30 +3110,33 @@ msgstr "" #: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 #: terminal/models/component/task.py:17 msgid "Args" -msgstr "" +msgstr "アルグ" #: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 #: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 #: terminal/models/session/sharing.py:24 msgid "Creator" -msgstr "" +msgstr "作成者" #: ops/models/base.py:19 +#, fuzzy msgid "Account policy" -msgstr "" +msgstr "アカウントキー" #: ops/models/base.py:20 +#, fuzzy msgid "Last execution" -msgstr "" +msgstr "コマンド実行" #: ops/models/base.py:22 +#, fuzzy msgid "Date last run" -msgstr "" +msgstr "最終同期日" #: ops/models/base.py:51 ops/models/job.py:105 #: xpack/plugins/cloud/models.py:172 msgid "Result" -msgstr "" +msgstr "結果" #: ops/models/base.py:52 ops/models/job.py:106 msgid "Summary" @@ -2996,22 +3144,23 @@ msgstr "" #: ops/models/celery.py:55 terminal/models/component/task.py:18 msgid "Kwargs" -msgstr "" +msgstr "クワーグ" #: ops/models/celery.py:56 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 #: tickets/serializers/ticket/ticket.py:21 msgid "State" -msgstr "" +msgstr "状態" #: ops/models/celery.py:57 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" -msgstr "" +msgstr "終了" #: ops/models/celery.py:58 +#, fuzzy msgid "Date published" -msgstr "" +msgstr "終了日" #: ops/models/job.py:21 msgid "Adhoc" @@ -3063,53 +3212,57 @@ msgstr "" #: ops/notifications.py:17 msgid "Server performance" -msgstr "" +msgstr "サーバーのパフォーマンス" #: ops/notifications.py:23 msgid "Terminal health check warning" -msgstr "" +msgstr "ターミナルヘルスチェックの警告" #: ops/notifications.py:68 #, python-brace-format msgid "The terminal is offline: {name}" -msgstr "" +msgstr "ターミナルはオフラインです: {name}" #: ops/notifications.py:73 #, python-brace-format msgid "Disk used more than {max_threshold}%: => {value}" -msgstr "" +msgstr "{max_threshold}%: => {value} を超えるディスクを使用" #: ops/notifications.py:78 #, python-brace-format msgid "Memory used more than {max_threshold}%: => {value}" -msgstr "" +msgstr "{max_threshold}%: => {value} を超える使用メモリ" #: ops/notifications.py:83 #, python-brace-format msgid "CPU load more than {max_threshold}: => {value}" -msgstr "" +msgstr "{max_threshold} を超えるCPUロード: => {value}" #: ops/serializers/job.py:10 +#, fuzzy msgid "Run after save" -msgstr "" +msgstr "システムユーザーの実行" #: ops/serializers/job.py:11 +#, fuzzy msgid "Job type" -msgstr "" +msgstr "Docタイプ" #: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 #: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" -msgstr "" +msgstr "タスク" #: ops/tasks.py:28 +#, fuzzy msgid "Run ansible task" -msgstr "" +msgstr "アセットの実行" #: ops/tasks.py:35 +#, fuzzy msgid "Run ansible task execution" -msgstr "" +msgstr "インスタンスタスクの同期実行" #: ops/tasks.py:48 msgid "Periodic clear celery tasks" @@ -3117,64 +3270,68 @@ msgstr "" #: ops/tasks.py:50 msgid "Clean celery log period" -msgstr "" +msgstr "きれいなセロリログ期間" #: ops/tasks.py:67 +#, fuzzy msgid "Clear celery periodic tasks" -msgstr "" +msgstr "きれいなセロリログ期間" #: ops/tasks.py:90 msgid "Create or update periodic tasks" msgstr "" #: ops/tasks.py:98 +#, fuzzy msgid "Periodic check service performance" -msgstr "" +msgstr "定期的なパフォーマンス" #: ops/templates/ops/celery_task_log.html:4 msgid "Task log" -msgstr "" +msgstr "タスクログ" #: ops/utils.py:64 msgid "Update task content: {}" -msgstr "" +msgstr "タスク内容の更新: {}" #: orgs/api.py:67 msgid "The current organization ({}) cannot be deleted" -msgstr "" +msgstr "現在の組織 ({}) は削除できません" #: orgs/api.py:72 msgid "" "LDAP synchronization is set to the current organization. Please switch to " "another organization before deleting" msgstr "" +"LDAP 同期は現在の組織に設定されます。削除する前に別の組織に切り替えてください" #: orgs/api.py:81 msgid "The organization have resource ({}) cannot be deleted" -msgstr "" +msgstr "組織のリソース ({}) は削除できません" #: orgs/apps.py:7 rbac/tree.py:113 msgid "App organizations" -msgstr "" +msgstr "アプリ組織" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 #: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:62 msgid "Organization" -msgstr "" +msgstr "組織" #: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 msgid "Org name" -msgstr "" +msgstr "組織名" #: orgs/models.py:72 +#, fuzzy msgid "Builtin" -msgstr "" +msgstr "内蔵" #: orgs/models.py:80 msgid "GLOBAL" -msgstr "" +msgstr "グローバル組織" #: orgs/models.py:82 msgid "DEFAULT" @@ -3186,27 +3343,29 @@ msgstr "" #: orgs/models.py:90 msgid "Can view root org" -msgstr "" +msgstr "グローバル組織を表示できます" #: orgs/models.py:91 msgid "Can view all joined org" -msgstr "" +msgstr "参加しているすべての組織を表示できます" #: orgs/tasks.py:9 +#, fuzzy msgid "Refresh organization cache" -msgstr "" +msgstr "グローバル組織名" #: perms/apps.py:9 msgid "App permissions" -msgstr "" +msgstr "アプリの権限" #: perms/const.py:12 msgid "Connect" -msgstr "" +msgstr "接続" #: perms/const.py:15 +#, fuzzy msgid "Copy" -msgstr "" +msgstr "リンクのコピー" #: perms/const.py:16 msgid "Paste" @@ -3217,76 +3376,78 @@ msgid "Transfer" msgstr "" #: perms/const.py:27 +#, fuzzy msgid "Clipboard" -msgstr "" +msgstr "クリップボードのコピー" #: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 #: perms/serializers/permission.py:29 perms/serializers/permission.py:59 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:18 msgid "Actions" -msgstr "" +msgstr "アクション" #: perms/models/asset_permission.py:73 msgid "From ticket" -msgstr "" +msgstr "チケットから" #: perms/models/asset_permission.py:81 msgid "Asset permission" -msgstr "" +msgstr "資産権限" #: perms/models/perm_node.py:55 msgid "Ungrouped" -msgstr "" +msgstr "グループ化されていません" #: perms/models/perm_node.py:57 msgid "Favorite" -msgstr "" +msgstr "お気に入り" #: perms/models/perm_node.py:104 msgid "Permed asset" -msgstr "" +msgstr "許可された資産" #: perms/models/perm_node.py:106 msgid "Can view my assets" -msgstr "" +msgstr "私の資産を見ることができます" #: perms/models/perm_node.py:107 msgid "Can view user assets" -msgstr "" +msgstr "ユーザー資産を表示できます" #: perms/models/perm_node.py:108 msgid "Can view usergroup assets" -msgstr "" +msgstr "ユーザーグループの資産を表示できます" #: perms/models/perm_node.py:119 +#, fuzzy msgid "Permed account" -msgstr "" +msgstr "アカウントを集める" #: perms/notifications.py:12 perms/notifications.py:44 msgid "today" -msgstr "" +msgstr "今" #: perms/notifications.py:15 msgid "You permed assets is about to expire" -msgstr "" +msgstr "パーマ資産の有効期限が近づいています" #: perms/notifications.py:20 msgid "permed assets" -msgstr "" +msgstr "パーマ資産" #: perms/notifications.py:59 msgid "Asset permissions is about to expire" -msgstr "" +msgstr "資産権限の有効期限が近づいています" #: perms/notifications.py:64 msgid "asset permissions of organization {}" -msgstr "" +msgstr "組織 {} の資産権限" #: perms/serializers/permission.py:31 perms/serializers/permission.py:60 #: users/serializers/user.py:100 users/serializers/user.py:205 msgid "Is expired" -msgstr "" +msgstr "期限切れです" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 @@ -3296,667 +3457,675 @@ msgid "" " The following %(item_type)s will expire in %(count)s days\n" " " msgstr "" +"\n" +" 次の %(item_type)s は %(count)s 日以内に期限切れになります\n" +" " #: perms/templates/perms/_msg_permed_items_expire.html:21 msgid "If you have any question, please contact the administrator" -msgstr "" +msgstr "質問があったら、管理者に連絡して下さい" #: perms/utils/user_permission.py:627 rbac/tree.py:57 msgid "My assets" -msgstr "" +msgstr "私の資産" #: rbac/api/role.py:34 msgid "Internal role, can't be destroy" -msgstr "" +msgstr "内部の役割は、破壊することはできません" #: rbac/api/role.py:38 msgid "The role has been bound to users, can't be destroy" -msgstr "" +msgstr "ロールはユーザーにバインドされており、破壊することはできません" #: rbac/api/role.py:60 msgid "Internal role, can't be update" -msgstr "" +msgstr "内部ロール、更新できません" #: rbac/api/rolebinding.py:52 msgid "{} at least one system role" -msgstr "" +msgstr "{} 少なくとも1つのシステムロール" #: rbac/apps.py:7 msgid "RBAC" -msgstr "" +msgstr "RBAC" #: rbac/builtin.py:111 msgid "SystemAdmin" -msgstr "" +msgstr "システム管理者" #: rbac/builtin.py:114 msgid "SystemAuditor" -msgstr "" +msgstr "システム監査人" #: rbac/builtin.py:117 msgid "SystemComponent" -msgstr "" +msgstr "システムコンポーネント" #: rbac/builtin.py:123 msgid "OrgAdmin" -msgstr "" +msgstr "組織管理者" #: rbac/builtin.py:126 msgid "OrgAuditor" -msgstr "" +msgstr "監査員を組織する" #: rbac/builtin.py:129 msgid "OrgUser" -msgstr "" +msgstr "組織ユーザー" #: rbac/models/menu.py:13 msgid "Menu permission" -msgstr "" +msgstr "メニュー権限" #: rbac/models/menu.py:15 msgid "Can view console view" -msgstr "" +msgstr "コンソールビューを表示できます" #: rbac/models/menu.py:16 msgid "Can view audit view" -msgstr "" +msgstr "監査ビューを表示できます" #: rbac/models/menu.py:17 msgid "Can view workbench view" -msgstr "" +msgstr "ワークスペースビューを表示できます" #: rbac/models/menu.py:18 msgid "Can view web terminal" -msgstr "" +msgstr "Webターミナルを表示できます" #: rbac/models/menu.py:19 msgid "Can view file manager" -msgstr "" +msgstr "ファイルマネージャを表示できます" #: rbac/models/permission.py:26 rbac/models/role.py:34 msgid "Permissions" -msgstr "" +msgstr "権限" #: rbac/models/role.py:31 rbac/models/rolebinding.py:38 #: settings/serializers/auth/oauth2.py:37 msgid "Scope" -msgstr "" +msgstr "スコープ" #: rbac/models/role.py:36 msgid "Built-in" -msgstr "" +msgstr "内蔵" #: rbac/models/role.py:46 rbac/models/rolebinding.py:44 #: users/models/user.py:685 msgid "Role" -msgstr "" +msgstr "ロール" #: rbac/models/role.py:144 msgid "System role" -msgstr "" +msgstr "システムの役割" #: rbac/models/role.py:152 msgid "Organization role" -msgstr "" +msgstr "組織の役割" #: rbac/models/rolebinding.py:53 msgid "Role binding" -msgstr "" +msgstr "ロールバインディング" #: rbac/models/rolebinding.py:137 msgid "All organizations" -msgstr "" +msgstr "全ての組織" #: rbac/models/rolebinding.py:166 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "" +"ユーザーの最後のロールは削除できません。ユーザーを組織から削除できます。" #: rbac/models/rolebinding.py:173 msgid "Organization role binding" -msgstr "" +msgstr "組織の役割バインディング" #: rbac/models/rolebinding.py:188 msgid "System role binding" -msgstr "" +msgstr "システムロールバインディング" #: rbac/serializers/permission.py:26 users/serializers/profile.py:132 msgid "Perms" -msgstr "" +msgstr "パーマ" #: rbac/serializers/role.py:11 msgid "Scope display" -msgstr "" +msgstr "スコープ表示" #: rbac/serializers/role.py:26 users/serializers/group.py:34 msgid "Users amount" -msgstr "" +msgstr "ユーザー数" #: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 msgid "Display name" -msgstr "" +msgstr "表示名" #: rbac/serializers/rolebinding.py:22 msgid "Role display" -msgstr "" +msgstr "ロール表示" #: rbac/serializers/rolebinding.py:56 msgid "Has bound this role" -msgstr "" +msgstr "この役割をバインドしました" #: rbac/tree.py:18 rbac/tree.py:19 msgid "All permissions" -msgstr "" +msgstr "すべての権限" #: rbac/tree.py:25 msgid "Console view" -msgstr "" +msgstr "コンソールビュー" #: rbac/tree.py:26 msgid "Workbench view" -msgstr "" +msgstr "ワークスペースビュー" #: rbac/tree.py:27 msgid "Audit view" -msgstr "" +msgstr "監査ビュー" #: rbac/tree.py:28 settings/models.py:156 msgid "System setting" -msgstr "" +msgstr "システム設定" #: rbac/tree.py:29 msgid "Other" -msgstr "" +msgstr "その他" #: rbac/tree.py:41 msgid "Session audits" -msgstr "" +msgstr "セッション監査" #: rbac/tree.py:51 msgid "Cloud import" -msgstr "" +msgstr "クラウドインポート" #: rbac/tree.py:52 msgid "Backup account" -msgstr "" +msgstr "バックアップアカウント" #: rbac/tree.py:53 msgid "Gather account" -msgstr "" +msgstr "アカウントを集める" #: rbac/tree.py:54 msgid "App change auth" -msgstr "" +msgstr "応用改密" #: rbac/tree.py:55 msgid "Asset change auth" -msgstr "" +msgstr "資産の改ざん" #: rbac/tree.py:56 msgid "Terminal setting" -msgstr "" +msgstr "ターミナル設定" #: rbac/tree.py:58 msgid "My apps" -msgstr "" +msgstr "マイアプリ" #: rbac/tree.py:114 msgid "Ticket comment" -msgstr "" +msgstr "チケットコメント" #: rbac/tree.py:115 tickets/models/ticket/general.py:306 msgid "Ticket" -msgstr "" +msgstr "チケット" #: rbac/tree.py:116 msgid "Common setting" -msgstr "" +msgstr "共通設定" #: rbac/tree.py:117 msgid "View permission tree" -msgstr "" +msgstr "権限ツリーの表示" #: rbac/tree.py:118 msgid "Execute batch command" -msgstr "" +msgstr "バッチ実行コマンド" #: settings/api/dingtalk.py:31 settings/api/feishu.py:36 #: settings/api/sms.py:148 settings/api/wecom.py:37 msgid "Test success" -msgstr "" +msgstr "テストの成功" #: settings/api/email.py:20 msgid "Test mail sent to {}, please check" -msgstr "" +msgstr "{}に送信されたテストメールを確認してください" #: settings/api/ldap.py:166 msgid "Synchronization start, please wait." -msgstr "" +msgstr "同期開始、お待ちください。" #: settings/api/ldap.py:170 msgid "Synchronization is running, please wait." -msgstr "" +msgstr "同期が実行中です。しばらくお待ちください。" #: settings/api/ldap.py:175 msgid "Synchronization error: {}" -msgstr "" +msgstr "同期エラー: {}" #: settings/api/ldap.py:213 msgid "Get ldap users is None" -msgstr "" +msgstr "Ldapユーザーを取得するにはNone" #: settings/api/ldap.py:222 msgid "Imported {} users successfully (Organization: {})" -msgstr "" +msgstr "{} 人のユーザーを正常にインポートしました (組織: {})" #: settings/api/sms.py:130 msgid "Invalid SMS platform" -msgstr "" +msgstr "無効なショートメッセージプラットフォーム" #: settings/api/sms.py:136 msgid "test_phone is required" -msgstr "" +msgstr "携帯番号をテストこのフィールドは必須です" #: settings/apps.py:7 msgid "Settings" -msgstr "" +msgstr "設定" #: settings/models.py:36 msgid "Encrypted" -msgstr "" +msgstr "暗号化された" #: settings/models.py:158 msgid "Can change email setting" -msgstr "" +msgstr "メール設定を変更できます" #: settings/models.py:159 msgid "Can change auth setting" -msgstr "" +msgstr "資格認定の設定" #: settings/models.py:160 msgid "Can change system msg sub setting" -msgstr "" +msgstr "システムmsgサブ设定を変更できます" #: settings/models.py:161 msgid "Can change sms setting" -msgstr "" +msgstr "Smsの設定を変えることができます" #: settings/models.py:162 msgid "Can change security setting" -msgstr "" +msgstr "セキュリティ設定を変更できます" #: settings/models.py:163 msgid "Can change clean setting" -msgstr "" +msgstr "きれいな設定を変えることができます" #: settings/models.py:164 msgid "Can change interface setting" -msgstr "" +msgstr "インターフェイスの設定を変えることができます" #: settings/models.py:165 msgid "Can change license setting" -msgstr "" +msgstr "ライセンス設定を変更できます" #: settings/models.py:166 msgid "Can change terminal setting" -msgstr "" +msgstr "ターミナルの設定を変えることができます" #: settings/models.py:167 msgid "Can change other setting" -msgstr "" +msgstr "他の設定を変えることができます" #: settings/serializers/auth/base.py:12 msgid "CAS Auth" -msgstr "" +msgstr "CAS 認証" #: settings/serializers/auth/base.py:13 msgid "OPENID Auth" -msgstr "" +msgstr "OPENID 認証" #: settings/serializers/auth/base.py:14 msgid "RADIUS Auth" -msgstr "" +msgstr "RADIUS 認証" #: settings/serializers/auth/base.py:15 msgid "DingTalk Auth" -msgstr "" +msgstr "くぎ 認証" #: settings/serializers/auth/base.py:16 msgid "FeiShu Auth" -msgstr "" +msgstr "飛本 認証" #: settings/serializers/auth/base.py:17 msgid "WeCom Auth" -msgstr "" +msgstr "企業微信 認証" #: settings/serializers/auth/base.py:18 msgid "SSO Auth" -msgstr "" +msgstr "SSO Token 認証" #: settings/serializers/auth/base.py:19 msgid "SAML2 Auth" -msgstr "" +msgstr "SAML2 認証" #: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 msgid "Forgot password url" -msgstr "" +msgstr "パスワードのURLを忘れた" #: settings/serializers/auth/base.py:28 msgid "Enable login redirect msg" -msgstr "" +msgstr "ログインリダイレクトの有効化msg" #: settings/serializers/auth/cas.py:10 msgid "CAS" -msgstr "" +msgstr "CAS" #: settings/serializers/auth/cas.py:12 msgid "Enable CAS Auth" -msgstr "" +msgstr "CAS 認証の有効化" #: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 msgid "Server url" -msgstr "" +msgstr "サービス側アドレス" #: settings/serializers/auth/cas.py:16 msgid "Proxy server url" -msgstr "" +msgstr "コールバックアドレス" #: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:55 #: settings/serializers/auth/saml2.py:34 msgid "Logout completely" -msgstr "" +msgstr "同期ログアウト" #: settings/serializers/auth/cas.py:23 msgid "Username attr" -msgstr "" +msgstr "ユーザー名のプロパティ" #: settings/serializers/auth/cas.py:26 msgid "Enable attributes map" -msgstr "" +msgstr "属性マップの有効化" #: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 msgid "Rename attr" -msgstr "" +msgstr "マッピングのプロパティ" #: settings/serializers/auth/cas.py:29 msgid "Create user if not" -msgstr "" +msgstr "そうでない場合はユーザーを作成" #: settings/serializers/auth/dingtalk.py:15 msgid "Enable DingTalk Auth" -msgstr "" +msgstr "ピン認証の有効化" #: settings/serializers/auth/feishu.py:14 msgid "Enable FeiShu Auth" -msgstr "" +msgstr "飛本認証の有効化" #: settings/serializers/auth/ldap.py:39 msgid "LDAP" -msgstr "" +msgstr "LDAP" #: settings/serializers/auth/ldap.py:42 msgid "LDAP server" -msgstr "" +msgstr "LDAPサーバー" #: settings/serializers/auth/ldap.py:43 msgid "eg: ldap://localhost:389" -msgstr "" +msgstr "例: ldap://localhost:389" #: settings/serializers/auth/ldap.py:45 msgid "Bind DN" -msgstr "" +msgstr "DN のバインド" #: settings/serializers/auth/ldap.py:50 msgid "User OU" -msgstr "" +msgstr "ユーザー OU" #: settings/serializers/auth/ldap.py:51 msgid "Use | split multi OUs" -msgstr "" +msgstr "使用 | splitマルチ OU" #: settings/serializers/auth/ldap.py:54 msgid "User search filter" -msgstr "" +msgstr "ユーザー検索フィルター" #: settings/serializers/auth/ldap.py:55 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" -msgstr "" +msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)" #: settings/serializers/auth/ldap.py:58 settings/serializers/auth/oauth2.py:57 #: settings/serializers/auth/oidc.py:37 msgid "User attr map" -msgstr "" +msgstr "ユーザー属性マッピング" #: settings/serializers/auth/ldap.py:59 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" msgstr "" +"ユーザー属性マッピングは、LDAPのユーザー属性をjumpserverユーザーにマッピング" +"する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" #: settings/serializers/auth/ldap.py:77 msgid "Connect timeout" -msgstr "" +msgstr "接続タイムアウト" #: settings/serializers/auth/ldap.py:79 msgid "Search paged size" -msgstr "" +msgstr "ページサイズを検索" #: settings/serializers/auth/ldap.py:81 msgid "Enable LDAP auth" -msgstr "" +msgstr "LDAP認証の有効化" #: settings/serializers/auth/oauth2.py:19 msgid "OAuth2" -msgstr "" +msgstr "OAuth2" #: settings/serializers/auth/oauth2.py:22 msgid "Enable OAuth2 Auth" -msgstr "" +msgstr "OAuth2認証の有効化" #: settings/serializers/auth/oauth2.py:25 msgid "Logo" -msgstr "" +msgstr "アイコン" #: settings/serializers/auth/oauth2.py:28 msgid "Service provider" -msgstr "" +msgstr "サービスプロバイダー" #: settings/serializers/auth/oauth2.py:31 settings/serializers/auth/oidc.py:19 msgid "Client Id" -msgstr "" +msgstr "クライアントID" #: settings/serializers/auth/oauth2.py:34 settings/serializers/auth/oidc.py:22 #: xpack/plugins/cloud/serializers/account_attrs.py:38 msgid "Client Secret" -msgstr "" +msgstr "クライアント秘密" #: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 msgid "Provider auth endpoint" -msgstr "" +msgstr "認証エンドポイントアドレス" #: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 msgid "Provider token endpoint" -msgstr "" +msgstr "プロバイダートークンエンドポイント" #: settings/serializers/auth/oauth2.py:46 settings/serializers/auth/oidc.py:30 msgid "Client authentication method" -msgstr "" +msgstr "クライアント認証方式" #: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 msgid "Provider userinfo endpoint" -msgstr "" +msgstr "プロバイダーuserinfoエンドポイント" #: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 msgid "Provider end session endpoint" -msgstr "" +msgstr "プロバイダーのセッション終了エンドポイント" #: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 #: settings/serializers/auth/saml2.py:35 msgid "Always update user" -msgstr "" +msgstr "常にユーザーを更新" #: settings/serializers/auth/oidc.py:12 msgid "OIDC" -msgstr "" +msgstr "OIDC" #: settings/serializers/auth/oidc.py:16 msgid "Base site url" -msgstr "" +msgstr "ベースサイトのアドレス" #: settings/serializers/auth/oidc.py:32 msgid "Share session" -msgstr "" +msgstr "セッションの共有" #: settings/serializers/auth/oidc.py:34 msgid "Ignore ssl verification" -msgstr "" +msgstr "Ssl検証を無視する" #: settings/serializers/auth/oidc.py:38 msgid "" "User attr map present how to map OpenID user attr to jumpserver, username," "name,email is jumpserver attr" msgstr "" +"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン" +"グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です" #: settings/serializers/auth/oidc.py:46 msgid "Use Keycloak" -msgstr "" +msgstr "Keycloakを使用する" #: settings/serializers/auth/oidc.py:52 msgid "Realm name" -msgstr "" +msgstr "レルム名" #: settings/serializers/auth/oidc.py:58 msgid "Enable OPENID Auth" -msgstr "" +msgstr "OIDC認証の有効化" #: settings/serializers/auth/oidc.py:60 msgid "Provider endpoint" -msgstr "" +msgstr "プロバイダーエンドポイント" #: settings/serializers/auth/oidc.py:69 msgid "Provider jwks endpoint" -msgstr "" +msgstr "プロバイダーjwksエンドポイント" #: settings/serializers/auth/oidc.py:78 msgid "Provider sign alg" -msgstr "" +msgstr "プロビダーサインalg" #: settings/serializers/auth/oidc.py:81 msgid "Provider sign key" -msgstr "" +msgstr "プロバイダ署名キー" #: settings/serializers/auth/oidc.py:83 msgid "Scopes" -msgstr "" +msgstr "スコープ" #: settings/serializers/auth/oidc.py:85 msgid "Id token max age" -msgstr "" +msgstr "IDトークンの最大年齢" #: settings/serializers/auth/oidc.py:88 msgid "Id token include claims" -msgstr "" +msgstr "IDトークンにはクレームが含まれます" #: settings/serializers/auth/oidc.py:90 msgid "Use state" -msgstr "" +msgstr "使用状態" #: settings/serializers/auth/oidc.py:91 msgid "Use nonce" -msgstr "" +msgstr "Nonceを使用" #: settings/serializers/auth/radius.py:13 msgid "Radius" -msgstr "" +msgstr "Radius" #: settings/serializers/auth/radius.py:15 msgid "Enable Radius Auth" -msgstr "" +msgstr "Radius認証の有効化" #: settings/serializers/auth/radius.py:21 msgid "OTP in Radius" -msgstr "" +msgstr "Radius のOTP" #: settings/serializers/auth/saml2.py:11 msgid "SAML2" -msgstr "" +msgstr "SAML2" #: settings/serializers/auth/saml2.py:14 msgid "Enable SAML2 Auth" -msgstr "" +msgstr "SAML2認証の有効化" #: settings/serializers/auth/saml2.py:17 msgid "IDP metadata URL" -msgstr "" +msgstr "IDP metadata アドレス" #: settings/serializers/auth/saml2.py:20 msgid "IDP metadata XML" -msgstr "" +msgstr "IDP metadata XML" #: settings/serializers/auth/saml2.py:23 msgid "SP advanced settings" -msgstr "" +msgstr "詳細設定" #: settings/serializers/auth/saml2.py:27 msgid "SP private key" -msgstr "" +msgstr "SP プライベートキー" #: settings/serializers/auth/saml2.py:31 msgid "SP cert" -msgstr "" +msgstr "SP 証明書" #: settings/serializers/auth/sms.py:15 msgid "Enable SMS" -msgstr "" +msgstr "SMSの有効化" #: settings/serializers/auth/sms.py:17 msgid "SMS provider / Protocol" -msgstr "" +msgstr "SMSプロバイダ / プロトコル" #: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:45 #: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 #: settings/serializers/auth/sms.py:73 settings/serializers/email.py:68 msgid "Signature" -msgstr "" +msgstr "署名" #: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:46 #: settings/serializers/auth/sms.py:54 settings/serializers/auth/sms.py:63 msgid "Template code" -msgstr "" +msgstr "テンプレートコード" #: settings/serializers/auth/sms.py:31 msgid "Test phone" -msgstr "" +msgstr "テスト電話" #: settings/serializers/auth/sms.py:60 msgid "App Access Address" -msgstr "" +msgstr "アプリケーションアドレス" #: settings/serializers/auth/sms.py:61 msgid "Signature channel number" -msgstr "" +msgstr "署名チャネル番号" #: settings/serializers/auth/sms.py:69 msgid "Enterprise code(SP id)" -msgstr "" +msgstr "企業コード(SP id)" #: settings/serializers/auth/sms.py:70 msgid "Shared secret(Shared secret)" -msgstr "" +msgstr "パスワードを共有する(Shared secret)" #: settings/serializers/auth/sms.py:71 msgid "Original number(Src id)" -msgstr "" +msgstr "元の番号(Src id)" #: settings/serializers/auth/sms.py:72 msgid "Business type(Service id)" -msgstr "" +msgstr "ビジネス・タイプ(Service id)" #: settings/serializers/auth/sms.py:75 msgid "Template" -msgstr "" +msgstr "テンプレート" #: settings/serializers/auth/sms.py:76 #, python-brace-format @@ -3965,200 +4134,213 @@ msgid "" "67 words. For example, your verification code is {code}, which is valid for " "5 minutes. Please do not disclose it to others." msgstr "" +"テンプレートには{code}を含める必要があり、署名+テンプレートの長さは67ワード未" +"満です。たとえば、認証コードは{code}で、有効期間は5分です。他の人には言わない" +"でください。" #: settings/serializers/auth/sms.py:85 #, python-brace-format msgid "The template needs to contain {code}" -msgstr "" +msgstr "テンプレートには{code}を含める必要があります" #: settings/serializers/auth/sms.py:88 msgid "Signature + Template must not exceed 65 words" -msgstr "" +msgstr "署名+テンプレートの長さは65文字以内" #: settings/serializers/auth/sso.py:13 msgid "Enable SSO auth" -msgstr "" +msgstr "SSO Token認証の有効化" #: settings/serializers/auth/sso.py:14 msgid "Other service can using SSO token login to JumpServer without password" msgstr "" +"他のサービスはパスワードなしでJumpServerへのSSOトークンログインを使用できます" #: settings/serializers/auth/sso.py:17 msgid "SSO auth key TTL" -msgstr "" +msgstr "Token有効期間" #: settings/serializers/auth/sso.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "Unit: second" -msgstr "" +msgstr "単位: 秒" #: settings/serializers/auth/wecom.py:15 msgid "Enable WeCom Auth" -msgstr "" +msgstr "企業微信認証の有効化" #: settings/serializers/basic.py:9 msgid "Subject" -msgstr "" +msgstr "件名" #: settings/serializers/basic.py:13 msgid "More url" -msgstr "" +msgstr "もっとURL" #: settings/serializers/basic.py:30 msgid "Site url" -msgstr "" +msgstr "サイトURL" #: settings/serializers/basic.py:31 msgid "eg: http://dev.jumpserver.org:8080" -msgstr "" +msgstr "例えば: http://dev.jumpserver.org:8080" #: settings/serializers/basic.py:34 msgid "User guide url" -msgstr "" +msgstr "ユーザーガイドurl" #: settings/serializers/basic.py:35 msgid "User first login update profile done redirect to it" -msgstr "" +msgstr "ユーザーの最初のログイン更新プロファイルがリダイレクトされました" #: settings/serializers/basic.py:39 msgid "" "The forgot password url on login page, If you use ldap or cas external " "authentication, you can set it" msgstr "" +"ログインページでパスワードのURLを忘れてしまいました。ldapまたはcasの外部認証" +"を使用している場合は、設定できます。" #: settings/serializers/basic.py:43 msgid "Global organization name" -msgstr "" +msgstr "グローバル組織名" #: settings/serializers/basic.py:44 msgid "The name of global organization to display" -msgstr "" +msgstr "表示するグローバル組織の名前" #: settings/serializers/basic.py:46 msgid "Enable announcement" -msgstr "" +msgstr "アナウンスの有効化" #: settings/serializers/basic.py:47 msgid "Announcement" -msgstr "" +msgstr "発表" #: settings/serializers/basic.py:48 msgid "Enable tickets" -msgstr "" +msgstr "チケットを有効にする" #: settings/serializers/cleaning.py:8 msgid "Period clean" -msgstr "" +msgstr "定時清掃" #: settings/serializers/cleaning.py:12 msgid "Login log keep days" -msgstr "" +msgstr "ログインログは日数を保持します" #: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 #: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 #: settings/serializers/cleaning.py:28 msgid "Unit: day" -msgstr "" +msgstr "単位: 日" #: settings/serializers/cleaning.py:16 msgid "Task log keep days" -msgstr "" +msgstr "タスクログは日数を保持します" #: settings/serializers/cleaning.py:20 msgid "Operate log keep days" -msgstr "" +msgstr "ログ管理日を操作する" #: settings/serializers/cleaning.py:24 msgid "FTP log keep days" -msgstr "" +msgstr "ダウンロードのアップロード" #: settings/serializers/cleaning.py:28 msgid "Cloud sync record keep days" -msgstr "" +msgstr "クラウド同期レコードは日数を保持します" #: settings/serializers/cleaning.py:31 msgid "Session keep duration" -msgstr "" +msgstr "セッション維持期間" #: settings/serializers/cleaning.py:32 msgid "" "Unit: days, Session, record, command will be delete if more than duration, " "only in database" msgstr "" +"単位:日。セッション、録画、コマンドレコードがそれを超えると削除されます(デー" +"タベースストレージにのみ影響します。ossなどは影響しません」影響を受ける)" #: settings/serializers/email.py:21 msgid "SMTP host" -msgstr "" +msgstr "SMTPホスト" #: settings/serializers/email.py:22 msgid "SMTP port" -msgstr "" +msgstr "SMTPポート" #: settings/serializers/email.py:23 msgid "SMTP account" -msgstr "" +msgstr "SMTPアカウント" #: settings/serializers/email.py:25 msgid "SMTP password" -msgstr "" +msgstr "SMTPパスワード" #: settings/serializers/email.py:26 msgid "Tips: Some provider use token except password" -msgstr "" +msgstr "ヒント: 一部のプロバイダーはパスワード以外のトークンを使用します" #: settings/serializers/email.py:29 msgid "Send user" -msgstr "" +msgstr "ユーザーを送信" #: settings/serializers/email.py:30 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "" +"ヒント: 送信メールアカウント、送信アカウントとしてのデフォルトのSMTPアカウン" +"ト" #: settings/serializers/email.py:33 msgid "Test recipient" -msgstr "" +msgstr "テスト受信者" #: settings/serializers/email.py:34 msgid "Tips: Used only as a test mail recipient" -msgstr "" +msgstr "ヒント: テストメールの受信者としてのみ使用" #: settings/serializers/email.py:38 msgid "If SMTP port is 465, may be select" -msgstr "" +msgstr "SMTPポートが465の場合は、" #: settings/serializers/email.py:41 msgid "Use TLS" -msgstr "" +msgstr "TLSの使用" #: settings/serializers/email.py:42 msgid "If SMTP port is 587, may be select" -msgstr "" +msgstr "SMTPポートが587の場合は、" #: settings/serializers/email.py:45 msgid "Subject prefix" -msgstr "" +msgstr "件名プレフィックス" #: settings/serializers/email.py:54 msgid "Create user email subject" -msgstr "" +msgstr "ユーザーメール件名の作成" #: settings/serializers/email.py:55 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "" +"ヒント: ユーザーを作成するときに、メールの件名を送信します (例: アカウントを" +"正常に作成)" #: settings/serializers/email.py:59 msgid "Create user honorific" -msgstr "" +msgstr "ユーザー敬語の作成" #: settings/serializers/email.py:60 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "" +"ヒント: ユーザーを作成するときは、メールの敬語を送信します (例: こんにちは)" #: settings/serializers/email.py:64 msgid "Create user email content" -msgstr "" +msgstr "ユーザーのメールコンテンツを作成する" #: settings/serializers/email.py:65 #, python-brace-format @@ -4166,165 +4348,173 @@ msgid "" "Tips: When creating a user, send the content of the email, support " "{username} {name} {email} label" msgstr "" +"ヒント:ユーザーの作成時にパスワード設定メールの内容を送信し、{username}{name}" +"{email}ラベルをサポートします。" #: settings/serializers/email.py:69 msgid "Tips: Email signature (eg:jumpserver)" -msgstr "" +msgstr "ヒント: メール署名 (例: jumpserver)" #: settings/serializers/other.py:6 msgid "More..." -msgstr "" +msgstr "詳細..." #: settings/serializers/other.py:9 msgid "Email suffix" -msgstr "" +msgstr "メールのサフィックス" #: settings/serializers/other.py:10 msgid "" "This is used by default if no email is returned during SSO authentication" -msgstr "" +msgstr "これは、SSO認証中にメールが返されない場合にデフォルトで使用されます。" #: settings/serializers/other.py:14 msgid "OTP issuer name" -msgstr "" +msgstr "OTP発行者名" #: settings/serializers/other.py:18 msgid "OTP valid window" -msgstr "" +msgstr "OTP有効なウィンドウ" #: settings/serializers/other.py:23 msgid "CMD" -msgstr "" +msgstr "CMD" #: settings/serializers/other.py:24 msgid "PowerShell" -msgstr "" +msgstr "PowerShell" #: settings/serializers/other.py:26 msgid "Shell (Windows)" -msgstr "" +msgstr "シェル (Windows)" #: settings/serializers/other.py:27 msgid "The shell type used when Windows assets perform ansible tasks" msgstr "" +"Windowsアセットが実行可能なタスクを実行するときに使用されるシェルタイプ" #: settings/serializers/other.py:31 msgid "Perm ungroup node" -msgstr "" +msgstr "グループ化されていないノードを表示" #: settings/serializers/other.py:32 msgid "Perm single to ungroup node" msgstr "" +"グループ化されていないノードに個別に許可された資産を配置し、資産が存在する" +"ノードが表示されないようにしますが、そのノードが許可されていないという質問に" +"質問" #: settings/serializers/other.py:37 msgid "Ticket authorize default time" -msgstr "" +msgstr "デフォルト製造オーダ承認時間" #: settings/serializers/other.py:40 msgid "day" -msgstr "" +msgstr "日" #: settings/serializers/other.py:40 msgid "hour" -msgstr "" +msgstr "時" #: settings/serializers/other.py:41 msgid "Ticket authorize default time unit" -msgstr "" +msgstr "デフォルト製造オーダ承認時間単位" #: settings/serializers/other.py:44 msgid "Help Docs URL" -msgstr "" +msgstr "ドキュメントリンク" #: settings/serializers/other.py:45 msgid "default: http://docs.jumpserver.org" -msgstr "" +msgstr "デフォルト: http://docs.jumpserver.org" #: settings/serializers/other.py:49 msgid "Help Support URL" -msgstr "" +msgstr "サポートリンク" #: settings/serializers/other.py:50 msgid "default: http://www.jumpserver.org/support/" -msgstr "" +msgstr "デフォルト: http://www.jumpserver.org/support/" #: settings/serializers/security.py:10 msgid "Password minimum length" -msgstr "" +msgstr "パスワードの最小長" #: settings/serializers/security.py:14 msgid "Admin user password minimum length" -msgstr "" +msgstr "管理者ユーザーパスワードの最小長" #: settings/serializers/security.py:17 msgid "Must contain capital" -msgstr "" +msgstr "資本を含める必要があります" #: settings/serializers/security.py:20 msgid "Must contain lowercase" -msgstr "" +msgstr "小文字を含める必要があります。" #: settings/serializers/security.py:23 msgid "Must contain numeric" -msgstr "" +msgstr "数値を含める必要があります" #: settings/serializers/security.py:26 msgid "Must contain special" -msgstr "" +msgstr "特別な" #: settings/serializers/security.py:31 msgid "" "Unit: minute, If the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" +"単位: 分。ユーザーが限られた回数だけログインできなかった場合、この時間間隔で" +"はログインはできません。" #: settings/serializers/security.py:40 msgid "All users" -msgstr "" +msgstr "すべてのユーザー" #: settings/serializers/security.py:41 msgid "Only admin users" -msgstr "" +msgstr "管理者のみ" #: settings/serializers/security.py:43 msgid "Global MFA auth" -msgstr "" +msgstr "グローバル有効化MFA認証" #: settings/serializers/security.py:47 msgid "Third-party login users perform MFA authentication" -msgstr "" +msgstr "サードパーティのログインユーザーがMFA認証を実行" #: settings/serializers/security.py:48 msgid "The third-party login modes include OIDC, CAS, and SAML2" -msgstr "" +msgstr "サードパーティのログインモードには、OIDC、CAS、SAML2" #: settings/serializers/security.py:52 msgid "Limit the number of user login failures" -msgstr "" +msgstr "ユーザーログインの失敗数を制限する" #: settings/serializers/security.py:56 msgid "Block user login interval" -msgstr "" +msgstr "ユーザーのログイン間隔をブロックする" #: settings/serializers/security.py:61 msgid "Limit the number of IP login failures" -msgstr "" +msgstr "IPログイン失敗の数を制限する" #: settings/serializers/security.py:65 msgid "Block IP login interval" -msgstr "" +msgstr "IPログイン間隔をブロックする" #: settings/serializers/security.py:69 msgid "Login IP White List" -msgstr "" +msgstr "ログインIPホワイトリスト" #: settings/serializers/security.py:74 msgid "Login IP Black List" -msgstr "" +msgstr "ログインIPブラックリスト" #: settings/serializers/security.py:80 msgid "User password expiration" -msgstr "" +msgstr "ユーザーパスワードの有効期限" #: settings/serializers/security.py:82 msgid "" @@ -4333,146 +4523,160 @@ msgid "" "be automatic sent to the user by system within 5 days (daily) before the " "password expires" msgstr "" +"単位: 日。ユーザーがその期間中にパスワードを更新しなかった場合、ユーザーパス" +"ワードの有効期限が切れます。パスワードの有効期限が切れる前の5日 (毎日) 以内" +"に、パスワードの有効期限が切れるリマインダーメールがシステムからユーザーに自" +"動的に送信されます。" #: settings/serializers/security.py:89 msgid "Number of repeated historical passwords" -msgstr "" +msgstr "繰り返された履歴パスワードの数" #: settings/serializers/security.py:91 msgid "" "Tip: When the user resets the password, it cannot be the previous n " "historical passwords of the user" msgstr "" +"ヒント: ユーザーがパスワードをリセットすると、ユーザーの前のnの履歴パスワード" +"にすることはできません" #: settings/serializers/security.py:96 msgid "Only single device login" -msgstr "" +msgstr "単一デバイスログインのみ" #: settings/serializers/security.py:97 msgid "Next device login, pre login will be logout" -msgstr "" +msgstr "次のデバイスログイン、事前ログインはログアウトになります" #: settings/serializers/security.py:100 msgid "Only exist user login" -msgstr "" +msgstr "ユーザーログインのみ存在" #: settings/serializers/security.py:101 msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" -msgstr "" +msgstr "Enableの場合、ユーザーがまだ存在しない場合、CAS、OIDC authは失敗します" #: settings/serializers/security.py:104 msgid "Only from source login" -msgstr "" +msgstr "ソースログインからのみ" #: settings/serializers/security.py:105 msgid "Only log in from the user source property" -msgstr "" +msgstr "ユーザーソースのプロパティからのみログイン" #: settings/serializers/security.py:109 msgid "MFA verify TTL" -msgstr "" +msgstr "MFAはTTLを確認します" #: settings/serializers/security.py:111 msgid "" "Unit: second, The verification MFA takes effect only when you view the " "account password" msgstr "" +"単位: 2番目に、検証MFAはアカウントのパスワードを表示したときにのみ有効になり" +"ます。" #: settings/serializers/security.py:116 msgid "Enable Login dynamic code" -msgstr "" +msgstr "ログイン動的コードの有効化" #: settings/serializers/security.py:117 msgid "" "The password and additional code are sent to a third party authentication " "system for verification" msgstr "" +"パスワードと追加コードは、検証のためにサードパーティの認証システムに送信され" +"ます" #: settings/serializers/security.py:122 msgid "MFA in login page" -msgstr "" +msgstr "ログインページのMFA" #: settings/serializers/security.py:123 msgid "Eu security regulations(GDPR) require MFA to be on the login page" msgstr "" +"Euセキュリティ規制 (GDPR) では、MFAがログインページにある必要があります" #: settings/serializers/security.py:126 msgid "Enable Login captcha" -msgstr "" +msgstr "ログインcaptchaの有効化" #: settings/serializers/security.py:127 msgid "Enable captcha to prevent robot authentication" -msgstr "" +msgstr "Captchaを有効にしてロボット認証を防止する" #: settings/serializers/security.py:146 msgid "Security" -msgstr "" +msgstr "セキュリティ" #: settings/serializers/security.py:149 msgid "Enable terminal register" -msgstr "" +msgstr "ターミナルレジスタの有効化" #: settings/serializers/security.py:151 msgid "" "Allow terminal register, after all terminal setup, you should disable this " "for security" msgstr "" +"ターミナルレジスタを許可し、すべてのターミナルセットアップの後、セキュリティ" +"のためにこれを無効にする必要があります" #: settings/serializers/security.py:155 msgid "Enable watermark" -msgstr "" +msgstr "透かしの有効化" #: settings/serializers/security.py:156 msgid "Enabled, the web session and replay contains watermark information" -msgstr "" +msgstr "Webセッションとリプレイには透かし情報が含まれています。" #: settings/serializers/security.py:160 msgid "Connection max idle time" -msgstr "" +msgstr "接続最大アイドル時間" #: settings/serializers/security.py:161 msgid "If idle time more than it, disconnect connection Unit: minute" -msgstr "" +msgstr "アイドル時間がそれ以上の場合は、接続単位を切断します: 分" #: settings/serializers/security.py:164 msgid "Remember manual auth" -msgstr "" +msgstr "手動入力パスワードの保存" #: settings/serializers/security.py:167 msgid "Enable change auth secure mode" -msgstr "" +msgstr "安全モードの変更を有効にする" #: settings/serializers/security.py:170 msgid "Insecure command alert" -msgstr "" +msgstr "安全でないコマンドアラート" #: settings/serializers/security.py:173 msgid "Email recipient" -msgstr "" +msgstr "メール受信者" #: settings/serializers/security.py:174 msgid "Multiple user using , split" -msgstr "" +msgstr "複数のユーザーを使用して、分割" #: settings/serializers/security.py:177 msgid "Batch command execution" -msgstr "" +msgstr "バッチコマンドの実行" #: settings/serializers/security.py:178 msgid "Allow user run batch command or not using ansible" -msgstr "" +msgstr "ユーザー実行バッチコマンドを許可するか、ansibleを使用しない" #: settings/serializers/security.py:181 msgid "Session share" -msgstr "" +msgstr "セッション共有" #: settings/serializers/security.py:182 msgid "Enabled, Allows user active session to be shared with other users" msgstr "" +"ユーザーのアクティブなセッションを他のユーザーと共有できるようにします。" #: settings/serializers/security.py:185 msgid "Remote Login Protection" -msgstr "" +msgstr "リモートログイン保護" #: settings/serializers/security.py:187 msgid "" @@ -4480,224 +4684,237 @@ msgid "" "city. If the account is logged in from a common login city, the system sends " "a remote login reminder" msgstr "" +"システムは、ログインIPアドレスが共通のログイン都市に属しているかどうかを判断" +"します。アカウントが共通のログイン都市からログインしている場合、システムはリ" +"モートログインリマインダーを送信します" #: settings/serializers/terminal.py:15 msgid "Auto" -msgstr "" +msgstr "自動" #: settings/serializers/terminal.py:21 msgid "Password auth" -msgstr "" +msgstr "パスワード認証" #: settings/serializers/terminal.py:23 msgid "Public key auth" -msgstr "" +msgstr "鍵認証" #: settings/serializers/terminal.py:24 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" msgstr "" +"ヒント: AD/LDAPなどの他の認証方法を使用する場合は、サードパーティ製システムの" +"削除後にこの項目を無効にする必要があります, ログインも可能" #: settings/serializers/terminal.py:28 msgid "List sort by" -msgstr "" +msgstr "リストの並べ替え" #: settings/serializers/terminal.py:31 msgid "List page size" -msgstr "" +msgstr "ページサイズを一覧表示" #: settings/serializers/terminal.py:34 msgid "Telnet login regex" -msgstr "" +msgstr "Telnetログインregex" #: settings/serializers/terminal.py:35 msgid "" "Tips: The login success message varies with devices. if you cannot log in to " "the device through Telnet, set this parameter" msgstr "" +"ヒント: ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバ" +"イスにログインできない場合は、このパラメーターを設定します。" #: settings/serializers/terminal.py:38 msgid "Enable database proxy" -msgstr "" +msgstr "属性マップの有効化" #: settings/serializers/terminal.py:39 msgid "Enable Razor" -msgstr "" +msgstr "Razor の有効化" #: settings/serializers/terminal.py:40 msgid "Enable SSH Client" -msgstr "" +msgstr "SSH Clientの有効化" #: settings/serializers/terminal.py:51 msgid "Default graphics resolution" -msgstr "" +msgstr "デフォルトのグラフィック解像度" #: settings/serializers/terminal.py:52 msgid "" "Tip: Default resolution to use when connecting graphical assets in Luna pages" msgstr "" +"ヒント: Luna ページでグラフィック アセットを接続するときに使用するデフォルト" +"の解像度" #: settings/utils/ldap.py:467 msgid "ldap:// or ldaps:// protocol is used." -msgstr "" +msgstr "ldap:// または ldaps:// プロトコルが使用されます。" #: settings/utils/ldap.py:478 msgid "Host or port is disconnected: {}" -msgstr "" +msgstr "ホストまたはポートが切断されました: {}" #: settings/utils/ldap.py:480 msgid "The port is not the port of the LDAP service: {}" -msgstr "" +msgstr "ポートはLDAPサービスのポートではありません: {}" #: settings/utils/ldap.py:482 msgid "Please add certificate: {}" -msgstr "" +msgstr "証明書を追加してください: {}" #: settings/utils/ldap.py:486 settings/utils/ldap.py:513 #: settings/utils/ldap.py:543 settings/utils/ldap.py:571 msgid "Unknown error: {}" -msgstr "" +msgstr "不明なエラー: {}" #: settings/utils/ldap.py:500 msgid "Bind DN or Password incorrect" -msgstr "" +msgstr "DNまたはパスワードのバインドが正しくありません" #: settings/utils/ldap.py:507 msgid "Please enter Bind DN: {}" -msgstr "" +msgstr "バインドDN: {} を入力してください" #: settings/utils/ldap.py:509 msgid "Please enter Password: {}" -msgstr "" +msgstr "パスワードを入力してください: {}" #: settings/utils/ldap.py:511 msgid "Please enter correct Bind DN and Password: {}" -msgstr "" +msgstr "正しいバインドDNとパスワードを入力してください: {}" #: settings/utils/ldap.py:529 msgid "Invalid User OU or User search filter: {}" -msgstr "" +msgstr "無効なユーザー OU またはユーザー検索フィルター: {}" #: settings/utils/ldap.py:560 msgid "LDAP User attr map not include: {}" -msgstr "" +msgstr "LDAP ユーザーattrマップは含まれません: {}" #: settings/utils/ldap.py:567 msgid "LDAP User attr map is not dict" -msgstr "" +msgstr "LDAPユーザーattrマップはdictではありません" #: settings/utils/ldap.py:586 msgid "LDAP authentication is not enabled" -msgstr "" +msgstr "LDAP 認証が有効になっていない" #: settings/utils/ldap.py:604 msgid "Error (Invalid LDAP server): {}" -msgstr "" +msgstr "エラー (LDAPサーバーが無効): {}" #: settings/utils/ldap.py:606 msgid "Error (Invalid Bind DN): {}" -msgstr "" +msgstr "エラー (DNのバインドが無効): {}" #: settings/utils/ldap.py:608 msgid "Error (Invalid LDAP User attr map): {}" -msgstr "" +msgstr "エラー (LDAPユーザーattrマップが無効): {}" #: settings/utils/ldap.py:610 msgid "Error (Invalid User OU or User search filter): {}" -msgstr "" +msgstr "エラー (ユーザーOUまたはユーザー検索フィルターが無効): {}" #: settings/utils/ldap.py:612 msgid "Error (Not enabled LDAP authentication): {}" -msgstr "" +msgstr "エラー (LDAP認証が有効化されていません): {}" #: settings/utils/ldap.py:614 msgid "Error (Unknown): {}" -msgstr "" +msgstr "エラー (不明): {}" #: settings/utils/ldap.py:617 msgid "Succeed: Match {} s user" -msgstr "" +msgstr "成功: {} 人のユーザーに一致" #: settings/utils/ldap.py:650 msgid "Authentication failed (configuration incorrect): {}" -msgstr "" +msgstr "認証に失敗しました (設定が正しくありません): {}" #: settings/utils/ldap.py:654 msgid "Authentication failed (username or password incorrect): {}" -msgstr "" +msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" #: settings/utils/ldap.py:656 msgid "Authentication failed (Unknown): {}" -msgstr "" +msgstr "認証に失敗しました (不明): {}" #: settings/utils/ldap.py:659 msgid "Authentication success: {}" -msgstr "" +msgstr "認証成功: {}" #: templates/_csv_import_export.html:8 msgid "Export" -msgstr "" +msgstr "エクスポート" #: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 msgid "Import" -msgstr "" +msgstr "インポート" #: templates/_csv_import_modal.html:12 msgid "Download the imported template or use the exported CSV file format" msgstr "" +"インポートしたテンプレートをダウンロードするか、エクスポートしたCSVファイル形" +"式を使用する" #: templates/_csv_import_modal.html:13 msgid "Download the import template" -msgstr "" +msgstr "インポートテンプレートのダウンロード" #: templates/_csv_import_modal.html:17 templates/_csv_update_modal.html:17 msgid "Select the CSV file to import" -msgstr "" +msgstr "インポートするCSVファイルの選択" #: templates/_csv_import_modal.html:39 templates/_csv_update_modal.html:42 msgid "Please select file" -msgstr "" +msgstr "ファイルを選択してください" #: templates/_csv_update_modal.html:12 msgid "Download the update template or use the exported CSV file format" msgstr "" +"更新テンプレートをダウンロードするか、エクスポートしたCSVファイル形式を使用す" +"る" #: templates/_csv_update_modal.html:13 msgid "Download the update template" -msgstr "" +msgstr "更新テンプレートのダウンロード" #: templates/_header_bar.html:12 msgid "Help" -msgstr "" +msgstr "ヘルプ" #: templates/_header_bar.html:19 msgid "Docs" -msgstr "" +msgstr "ドキュメント" #: templates/_header_bar.html:25 msgid "Commercial support" -msgstr "" +msgstr "商用サポート" #: templates/_header_bar.html:76 users/forms/profile.py:44 msgid "Profile" -msgstr "" +msgstr "プロフィール" #: templates/_header_bar.html:79 msgid "Admin page" -msgstr "" +msgstr "ページの管理" #: templates/_header_bar.html:81 msgid "User page" -msgstr "" +msgstr "ユーザーページ" #: templates/_header_bar.html:84 msgid "API Key" -msgstr "" +msgstr "API Key" #: templates/_header_bar.html:85 msgid "Logout" -msgstr "" +msgstr "ログアウト" #: templates/_message.html:6 msgid "" @@ -4705,14 +4922,17 @@ msgid "" " Your account has expired, please contact the administrator.\n" " " msgstr "" +"\n" +" アカウントが期限切れになったので、管理者に連絡してくださ" +"い。 " #: templates/_message.html:13 msgid "Your account will at" -msgstr "" +msgstr "あなたのアカウントは" #: templates/_message.html:13 templates/_message.html:30 msgid "expired. " -msgstr "" +msgstr "期限切れです。" #: templates/_message.html:23 #, python-format @@ -4722,10 +4942,14 @@ msgid "" "href=\"%(user_password_update_url)s\"> this link update password.\n" " " msgstr "" +"\n" +" パスワードが期限切れになりましたので、クリックしてください " +" リンク パスワードの更新\n" +" " #: templates/_message.html:30 msgid "Your password will at" -msgstr "" +msgstr "あなたのパスワードは" #: templates/_message.html:31 #, python-format @@ -4735,6 +4959,10 @@ msgid "" "link to update your password.\n" " " msgstr "" +"\n" +" クリックしてください リンク パスワードの更新\n" +" " #: templates/_message.html:43 #, python-format @@ -4744,6 +4972,10 @@ msgid "" "href=\"%(first_login_url)s\"> this link to complete your information.\n" " " msgstr "" +"\n" +" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" +" " #: templates/_message.html:56 #, python-format @@ -4753,207 +4985,223 @@ msgid "" "href=\"%(user_pubkey_update)s\"> this link to update\n" " " msgstr "" +"\n" +" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" +" " #: templates/_mfa_login_field.html:28 msgid "Send verification code" -msgstr "" +msgstr "確認コードを送信" #: templates/_mfa_login_field.html:106 #: users/templates/users/forgot_password.html:129 msgid "Wait: " -msgstr "" +msgstr "待つ:" #: templates/_mfa_login_field.html:116 #: users/templates/users/forgot_password.html:145 msgid "The verification code has been sent" -msgstr "" +msgstr "確認コードが送信されました" #: templates/_without_nav_base.html:26 msgid "Home page" -msgstr "" +msgstr "ホームページ" #: templates/resource_download.html:18 templates/resource_download.html:31 msgid "Client" -msgstr "" +msgstr "クライアント" #: templates/resource_download.html:20 msgid "" "JumpServer Client, currently used to launch the client, now only support " "launch RDP SSH client, The Telnet client will next" msgstr "" +"JumpServerクライアントは、現在特定のクライアントプログラムの接続資産を喚起す" +"るために使用されており、現在はRDP SSHクライアントのみをサポートしています。" +"「Telnetは将来的にサポートする" #: templates/resource_download.html:31 msgid "Microsoft" -msgstr "" +msgstr "マイクロソフト" #: templates/resource_download.html:31 msgid "Official" -msgstr "" +msgstr "公式" #: templates/resource_download.html:33 msgid "" "macOS needs to download the client to connect RDP asset, which comes with " "Windows" msgstr "" +"MacOSは、Windowsに付属のRDPアセットを接続するためにクライアントをダウンロード" +"する必要があります" #: templates/resource_download.html:42 msgid "Windows Remote application publisher tools" -msgstr "" +msgstr "Windowsリモートアプリケーション発行者ツール" #: templates/resource_download.html:43 msgid "" "OpenSSH is a program used to connect remote applications in the Windows " "Remote Application Publisher" msgstr "" +"OpenSSHはリモートアプリケーションをWindowsリモートアプリケーションで接続する" +"プログラムです" #: templates/resource_download.html:48 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" msgstr "" +"Jmservisorはwindowsリモートアプリケーションパブリケーションサーバでリモートア" +"プリケーションを引き出すためのプログラムです" #: templates/resource_download.html:57 msgid "Offline video player" -msgstr "" +msgstr "オフラインビデオプレーヤー" #: terminal/api/component/endpoint.py:31 msgid "Not found protocol query params" -msgstr "" +msgstr "プロトコルクエリパラメータが見つかりません" #: terminal/api/component/storage.py:28 msgid "Deleting the default storage is not allowed" -msgstr "" +msgstr "デフォルトのストレージの削除は許可されていません" #: terminal/api/component/storage.py:31 msgid "Cannot delete storage that is being used" -msgstr "" +msgstr "使用中のストレージを削除できません" #: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 msgid "Command storages" -msgstr "" +msgstr "コマンドストア" #: terminal/api/component/storage.py:79 msgid "Invalid" -msgstr "" +msgstr "無効" #: terminal/api/component/storage.py:119 msgid "Test failure: {}" -msgstr "" +msgstr "テスト失敗: {}" #: terminal/api/component/storage.py:122 msgid "Test successful" -msgstr "" +msgstr "テスト成功" #: terminal/api/component/storage.py:124 msgid "Test failure: Account invalid" -msgstr "" +msgstr "テスト失敗: アカウントが無効" #: terminal/api/component/terminal.py:38 msgid "Have online sessions" -msgstr "" +msgstr "オンラインセッションを持つ" #: terminal/api/session/session.py:217 msgid "Session does not exist: {}" -msgstr "" +msgstr "セッションが存在しません: {}" #: terminal/api/session/session.py:220 msgid "Session is finished or the protocol not supported" -msgstr "" +msgstr "セッションが終了したか、プロトコルがサポートされていません" #: terminal/api/session/session.py:233 msgid "User does not have permission" -msgstr "" +msgstr "ユーザーに権限がありません" #: terminal/api/session/sharing.py:29 msgid "Secure session sharing settings is disabled" -msgstr "" +msgstr "安全なセッション共有設定が無効になっています" #: terminal/apps.py:9 msgid "Terminals" -msgstr "" +msgstr "ターミナル管理" #: terminal/backends/command/models.py:16 msgid "Ordinary" -msgstr "" +msgstr "普通" #: terminal/backends/command/models.py:17 msgid "Dangerous" -msgstr "" +msgstr "危険" #: terminal/backends/command/models.py:23 msgid "Input" -msgstr "" +msgstr "入力" #: terminal/backends/command/models.py:24 #: terminal/backends/command/serializers.py:38 msgid "Output" -msgstr "" +msgstr "出力" #: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 #: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 #: terminal/templates/terminal/_msg_command_alert.html:10 #: tickets/models/ticket/command_confirm.py:17 msgid "Session" -msgstr "" +msgstr "セッション" #: terminal/backends/command/models.py:26 #: terminal/backends/command/serializers.py:18 msgid "Risk level" -msgstr "" +msgstr "リスクレベル" #: terminal/backends/command/serializers.py:16 msgid "Session ID" -msgstr "" +msgstr "セッションID" #: terminal/backends/command/serializers.py:37 +#, fuzzy msgid "Account " -msgstr "" +msgstr "アカウント" #: terminal/backends/command/serializers.py:39 msgid "Risk level display" -msgstr "" +msgstr "リスクレベル表示" #: terminal/backends/command/serializers.py:40 msgid "Timestamp" -msgstr "" +msgstr "タイムスタンプ" #: terminal/backends/command/serializers.py:42 #: terminal/models/component/terminal.py:85 msgid "Remote Address" -msgstr "" +msgstr "リモートアドレス" #: terminal/const.py:37 msgid "Critical" -msgstr "" +msgstr "クリティカル" #: terminal/const.py:38 msgid "High" -msgstr "" +msgstr "高い" #: terminal/const.py:39 users/templates/users/reset_password.html:50 msgid "Normal" -msgstr "" +msgstr "正常" #: terminal/const.py:40 msgid "Offline" -msgstr "" +msgstr "オフライン" #: terminal/const.py:81 terminal/const.py:82 terminal/const.py:83 #: terminal/const.py:84 terminal/const.py:85 +#, fuzzy msgid "DB Client" -msgstr "" +msgstr "クライアント" #: terminal/exceptions.py:8 msgid "Bulk create not support" -msgstr "" +msgstr "一括作成非サポート" #: terminal/exceptions.py:13 msgid "Storage is invalid" -msgstr "" +msgstr "ストレージが無効です" #: terminal/models/applet/applet.py:23 +#, fuzzy msgid "Author" -msgstr "" +msgstr "資産アカウント" #: terminal/models/applet/applet.py:27 msgid "Tags" @@ -4961,31 +5209,36 @@ msgstr "" #: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 msgid "Hosts" -msgstr "" +msgstr "ホスト" #: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 +#, fuzzy msgid "Applet" -msgstr "" +msgstr "資産の適用" #: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 +#, fuzzy msgid "Deploy options" -msgstr "" +msgstr "その他のログインオプション" #: terminal/models/applet/host.py:19 msgid "Inited" msgstr "" #: terminal/models/applet/host.py:20 +#, fuzzy msgid "Date inited" -msgstr "" +msgstr "終了日" #: terminal/models/applet/host.py:21 +#, fuzzy msgid "Date synced" -msgstr "" +msgstr "日付の同期" #: terminal/models/applet/host.py:102 +#, fuzzy msgid "Hosting" -msgstr "" +msgstr "ホスト" #: terminal/models/applet/host.py:103 msgid "Initial" @@ -4993,19 +5246,19 @@ msgstr "" #: terminal/models/component/endpoint.py:15 msgid "HTTPS Port" -msgstr "" +msgstr "HTTPS ポート" #: terminal/models/component/endpoint.py:16 msgid "HTTP Port" -msgstr "" +msgstr "HTTP ポート" #: terminal/models/component/endpoint.py:17 msgid "SSH Port" -msgstr "" +msgstr "SSH ポート" #: terminal/models/component/endpoint.py:18 msgid "RDP Port" -msgstr "" +msgstr "RDP ポート" #: terminal/models/component/endpoint.py:25 #: terminal/models/component/endpoint.py:94 terminal/serializers/endpoint.py:57 @@ -5013,205 +5266,210 @@ msgstr "" #: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 #: terminal/serializers/storage.py:98 msgid "Endpoint" -msgstr "" +msgstr "エンドポイント" #: terminal/models/component/endpoint.py:87 msgid "IP group" -msgstr "" +msgstr "IP グループ" #: terminal/models/component/endpoint.py:99 msgid "Endpoint rule" -msgstr "" +msgstr "エンドポイントルール" #: terminal/models/component/status.py:14 msgid "Session Online" -msgstr "" +msgstr "セッションオンライン" #: terminal/models/component/status.py:15 msgid "CPU Load" -msgstr "" +msgstr "CPUロード" #: terminal/models/component/status.py:16 msgid "Memory Used" -msgstr "" +msgstr "使用メモリ" #: terminal/models/component/status.py:17 msgid "Disk Used" -msgstr "" +msgstr "使用済みディスク" #: terminal/models/component/status.py:18 msgid "Connections" -msgstr "" +msgstr "接続" #: terminal/models/component/status.py:19 msgid "Threads" -msgstr "" +msgstr "スレッド" #: terminal/models/component/status.py:20 msgid "Boot Time" -msgstr "" +msgstr "ブート時間" #: terminal/models/component/storage.py:27 msgid "Default storage" -msgstr "" +msgstr "デフォルトのストレージ" #: terminal/models/component/storage.py:140 #: terminal/models/component/terminal.py:86 msgid "Command storage" -msgstr "" +msgstr "コマンドストレージ" #: terminal/models/component/storage.py:200 #: terminal/models/component/terminal.py:87 msgid "Replay storage" -msgstr "" +msgstr "再生ストレージ" #: terminal/models/component/terminal.py:83 msgid "type" -msgstr "" +msgstr "タイプ" #: terminal/models/component/terminal.py:88 msgid "Application User" -msgstr "" +msgstr "ユーザーの適用" #: terminal/models/component/terminal.py:161 msgid "Can view terminal config" -msgstr "" +msgstr "ターミナル構成を表示できます" #: terminal/models/session/command.py:66 msgid "Command record" -msgstr "" +msgstr "コマンドレコード" #: terminal/models/session/replay.py:12 msgid "Session replay" -msgstr "" +msgstr "セッション再生" #: terminal/models/session/replay.py:14 msgid "Can upload session replay" -msgstr "" +msgstr "セッションのリプレイをアップロードできます" #: terminal/models/session/replay.py:15 msgid "Can download session replay" -msgstr "" +msgstr "セッション再生をダウンロードできます" #: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 msgid "Login from" -msgstr "" +msgstr "ログイン元" #: terminal/models/session/session.py:40 msgid "Replay" -msgstr "" +msgstr "リプレイ" #: terminal/models/session/session.py:44 msgid "Date end" -msgstr "" +msgstr "終了日" #: terminal/models/session/session.py:236 msgid "Session record" -msgstr "" +msgstr "セッション記録" #: terminal/models/session/session.py:238 msgid "Can monitor session" -msgstr "" +msgstr "セッションを監視できます" #: terminal/models/session/session.py:239 msgid "Can share session" -msgstr "" +msgstr "セッションを共有できます" #: terminal/models/session/session.py:240 msgid "Can terminate session" -msgstr "" +msgstr "セッションを終了できます" #: terminal/models/session/session.py:241 msgid "Can validate session action perm" -msgstr "" +msgstr "セッションアクションのパーマを検証できます" #: terminal/models/session/sharing.py:31 msgid "Expired time (min)" -msgstr "" +msgstr "期限切れ時間 (分)" #: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 msgid "Session sharing" -msgstr "" +msgstr "セッション共有" #: terminal/models/session/sharing.py:39 msgid "Can add super session sharing" -msgstr "" +msgstr "スーパーセッション共有を追加できます" #: terminal/models/session/sharing.py:66 msgid "Link not active" -msgstr "" +msgstr "リンクがアクティブでない" #: terminal/models/session/sharing.py:68 msgid "Link expired" -msgstr "" +msgstr "リンク期限切れ" #: terminal/models/session/sharing.py:70 msgid "User not allowed to join" -msgstr "" +msgstr "ユーザーはセッションに参加できません" #: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 msgid "Joiner" -msgstr "" +msgstr "ジョイナー" #: terminal/models/session/sharing.py:90 msgid "Date joined" -msgstr "" +msgstr "参加日" #: terminal/models/session/sharing.py:93 msgid "Date left" -msgstr "" +msgstr "日付が残っています" #: terminal/models/session/sharing.py:116 msgid "Session join record" -msgstr "" +msgstr "セッション参加記録" #: terminal/models/session/sharing.py:132 msgid "Invalid verification code" -msgstr "" +msgstr "検証コードが無効" #: terminal/notifications.py:22 msgid "Sessions" -msgstr "" +msgstr "セッション" #: terminal/notifications.py:68 msgid "Danger command alert" -msgstr "" +msgstr "危険コマンドアラート" #: terminal/notifications.py:95 terminal/notifications.py:143 msgid "Level" -msgstr "" +msgstr "レベル" #: terminal/notifications.py:113 msgid "Batch danger command alert" -msgstr "" +msgstr "一括危険コマンド警告" #: terminal/serializers/applet.py:16 +#, fuzzy msgid "Published" -msgstr "" +msgstr "公開キー" #: terminal/serializers/applet.py:17 +#, fuzzy msgid "Unpublished" -msgstr "" +msgstr "終了" #: terminal/serializers/applet.py:18 +#, fuzzy msgid "Not match" -msgstr "" +msgstr "ユーザーにマッチしなかった" #: terminal/serializers/applet.py:32 msgid "Icon" msgstr "" #: terminal/serializers/applet_host.py:21 +#, fuzzy msgid "Per Session" -msgstr "" +msgstr "セッション" #: terminal/serializers/applet_host.py:22 msgid "Per Device" msgstr "" #: terminal/serializers/applet_host.py:28 +#, fuzzy msgid "RDS Licensing" -msgstr "" +msgstr "ライセンス" #: terminal/serializers/applet_host.py:29 msgid "RDS License Server" @@ -5235,140 +5493,142 @@ msgstr "" #: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 msgid "Load status" -msgstr "" +msgstr "ロードステータス" #: terminal/serializers/endpoint.py:14 msgid "Magnus listen db port" -msgstr "" +msgstr "Magnus がリッスンするデータベース ポート" #: terminal/serializers/endpoint.py:17 msgid "Magnus Listen port range" -msgstr "" +msgstr "Magnus がリッスンするポート範囲" #: terminal/serializers/endpoint.py:19 msgid "" "The range of ports that Magnus listens on is modified in the configuration " "file" -msgstr "" +msgstr "Magnus がリッスンするポート範囲を構成ファイルで変更してください" #: terminal/serializers/endpoint.py:51 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "" +"異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使用" +"して実装します" #: terminal/serializers/session.py:17 terminal/serializers/session.py:42 msgid "Terminal display" -msgstr "" +msgstr "ターミナルディスプレイ" #: terminal/serializers/session.py:33 msgid "User ID" -msgstr "" +msgstr "ユーザーID" #: terminal/serializers/session.py:34 msgid "Asset ID" -msgstr "" +msgstr "資産ID" #: terminal/serializers/session.py:35 msgid "Login from display" -msgstr "" +msgstr "表示からのログイン" #: terminal/serializers/session.py:37 msgid "Can replay" -msgstr "" +msgstr "再生できます" #: terminal/serializers/session.py:38 msgid "Can join" -msgstr "" +msgstr "参加できます" #: terminal/serializers/session.py:39 msgid "Terminal ID" -msgstr "" +msgstr "ターミナル ID" #: terminal/serializers/session.py:40 msgid "Is finished" -msgstr "" +msgstr "終了しました" #: terminal/serializers/session.py:41 msgid "Can terminate" -msgstr "" +msgstr "終了できます" #: terminal/serializers/session.py:47 msgid "Command amount" -msgstr "" +msgstr "コマンド量" #: terminal/serializers/storage.py:20 msgid "Endpoint invalid: remove path `{}`" -msgstr "" +msgstr "エンドポイントが無効: パス '{}' を削除" #: terminal/serializers/storage.py:26 msgid "Bucket" -msgstr "" +msgstr "バケット" #: terminal/serializers/storage.py:30 #: xpack/plugins/cloud/serializers/account_attrs.py:17 msgid "Access key id" -msgstr "" +msgstr "アクセスキー" #: terminal/serializers/storage.py:34 #: xpack/plugins/cloud/serializers/account_attrs.py:20 msgid "Access key secret" -msgstr "" +msgstr "アクセスキーシークレット" #: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:219 msgid "Region" -msgstr "" +msgstr "リージョン" #: terminal/serializers/storage.py:109 msgid "Container name" -msgstr "" +msgstr "コンテナー名" #: terminal/serializers/storage.py:112 msgid "Account key" -msgstr "" +msgstr "アカウントキー" #: terminal/serializers/storage.py:115 msgid "Endpoint suffix" -msgstr "" +msgstr "エンドポイントサフィックス" #: terminal/serializers/storage.py:135 msgid "The address format is incorrect" -msgstr "" +msgstr "アドレス形式が正しくありません" #: terminal/serializers/storage.py:142 msgid "Host invalid" -msgstr "" +msgstr "ホスト無効" #: terminal/serializers/storage.py:145 msgid "Port invalid" -msgstr "" +msgstr "ポートが無効" #: terminal/serializers/storage.py:160 msgid "Index by date" -msgstr "" +msgstr "日付による索引付け" #: terminal/serializers/storage.py:161 msgid "Whether to create an index by date" -msgstr "" +msgstr "現在の日付に基づいてインデックスを動的に作成するかどうか" #: terminal/serializers/storage.py:164 msgid "Index" -msgstr "" +msgstr "インデックス" #: terminal/serializers/storage.py:166 msgid "Doc type" -msgstr "" +msgstr "Docタイプ" #: terminal/serializers/storage.py:168 msgid "Ignore Certificate Verification" -msgstr "" +msgstr "証明書の検証を無視する" #: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 msgid "Not found" -msgstr "" +msgstr "見つかりません" #: terminal/templates/terminal/_msg_command_alert.html:10 msgid "view" -msgstr "" +msgstr "表示" #: terminal/utils/db_port_mapper.py:64 msgid "" @@ -5376,28 +5636,33 @@ msgid "" "number of ports open to the database agent service, Contact the " "administrator to open more ports." msgstr "" +"利用可能なポートと一致しません。データベースの数が、データベース プロキシ " +"サービスによって開かれたポートの数を超えた可能性があります。さらにポートを開" +"くには、管理者に連絡してください。" #: terminal/utils/db_port_mapper.py:90 msgid "" "No ports can be used, check and modify the limit on the number of ports that " "Magnus listens on in the configuration file." msgstr "" +"使用できるポートがありません。設定ファイルで Magnus がリッスンするポート数の" +"制限を確認して変更してください. " #: terminal/utils/db_port_mapper.py:92 msgid "All available port count: {}, Already use port count: {}" -msgstr "" +msgstr "使用可能なすべてのポート数: {}、すでに使用しているポート数: {}" #: tickets/apps.py:7 msgid "Tickets" -msgstr "" +msgstr "チケット" #: tickets/const.py:9 msgid "Apply for asset" -msgstr "" +msgstr "資産の申請" #: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 msgid "Open" -msgstr "" +msgstr "オープン" #: tickets/const.py:18 tickets/const.py:31 msgid "Reopen" @@ -5405,69 +5670,71 @@ msgstr "" #: tickets/const.py:19 tickets/const.py:32 msgid "Approved" -msgstr "" +msgstr "承認済み" #: tickets/const.py:20 tickets/const.py:33 msgid "Rejected" -msgstr "" +msgstr "拒否" #: tickets/const.py:30 tickets/const.py:38 msgid "Closed" -msgstr "" +msgstr "クローズ" #: tickets/const.py:46 msgid "Approve" -msgstr "" +msgstr "承認" #: tickets/const.py:50 msgid "One level" -msgstr "" +msgstr "1つのレベル" #: tickets/const.py:51 msgid "Two level" -msgstr "" +msgstr "2つのレベル" #: tickets/const.py:55 msgid "Org admin" -msgstr "" +msgstr "Org admin" #: tickets/const.py:56 msgid "Custom user" -msgstr "" +msgstr "カスタムユーザー" #: tickets/const.py:57 msgid "Super admin" -msgstr "" +msgstr "スーパー管理者" #: tickets/const.py:58 msgid "Super admin and org admin" -msgstr "" +msgstr "スーパーadminとorg admin" #: tickets/errors.py:9 msgid "Ticket already closed" -msgstr "" +msgstr "チケットはすでに閉じています" #: tickets/handlers/apply_asset.py:36 msgid "" "Created by the ticket ticket title: {} ticket applicant: {} ticket " "processor: {} ticket ID: {}" msgstr "" +"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: " +"{}" #: tickets/handlers/base.py:84 msgid "Change field" -msgstr "" +msgstr "フィールドを変更" #: tickets/handlers/base.py:84 msgid "Before change" -msgstr "" +msgstr "変更前" #: tickets/handlers/base.py:84 msgid "After change" -msgstr "" +msgstr "変更後" #: tickets/handlers/base.py:96 msgid "{} {} the ticket" -msgstr "" +msgstr "{} {} チケット" #: tickets/models/comment.py:14 msgid "common" @@ -5475,291 +5742,297 @@ msgstr "" #: tickets/models/comment.py:23 msgid "User display name" -msgstr "" +msgstr "ユーザー表示名" #: tickets/models/comment.py:24 msgid "Body" -msgstr "" +msgstr "ボディ" #: tickets/models/flow.py:20 tickets/models/flow.py:62 #: tickets/models/ticket/general.py:39 msgid "Approve level" -msgstr "" +msgstr "レベルを承認する" #: tickets/models/flow.py:25 tickets/serializers/flow.py:18 msgid "Approve strategy" -msgstr "" +msgstr "戦略を承認する" #: tickets/models/flow.py:30 tickets/serializers/flow.py:20 msgid "Assignees" -msgstr "" +msgstr "アシニーズ" #: tickets/models/flow.py:34 msgid "Ticket flow approval rule" -msgstr "" +msgstr "チケットフロー承認ルール" #: tickets/models/flow.py:67 msgid "Ticket flow" -msgstr "" +msgstr "チケットの流れ" #: tickets/models/relation.py:10 msgid "Ticket session relation" -msgstr "" +msgstr "チケットセッションの関係" #: tickets/models/ticket/apply_application.py:10 #: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" -msgstr "" +msgstr "認可ルール名" #: tickets/models/ticket/apply_application.py:19 msgid "Apply applications" -msgstr "" +msgstr "アプリケーションの適用" #: tickets/models/ticket/apply_application.py:22 msgid "Apply system users" -msgstr "" +msgstr "システムユーザーの適用" #: tickets/models/ticket/apply_asset.py:9 #: tickets/serializers/ticket/apply_asset.py:14 msgid "Select at least one asset or node" -msgstr "" +msgstr "少なくとも1つのアセットまたはノードを選択します。" #: tickets/models/ticket/apply_asset.py:14 #: tickets/serializers/ticket/apply_asset.py:19 msgid "Apply nodes" -msgstr "" +msgstr "ノードの適用" #: tickets/models/ticket/apply_asset.py:16 #: tickets/serializers/ticket/apply_asset.py:18 msgid "Apply assets" -msgstr "" +msgstr "資産の適用" #: tickets/models/ticket/apply_asset.py:17 +#, fuzzy msgid "Apply accounts" -msgstr "" +msgstr "アプリケーションアカウント" #: tickets/models/ticket/command_confirm.py:10 msgid "Run user" -msgstr "" +msgstr "ユーザーの実行" #: tickets/models/ticket/command_confirm.py:12 msgid "Run asset" -msgstr "" +msgstr "アセットの実行" #: tickets/models/ticket/command_confirm.py:13 msgid "Run command" -msgstr "" +msgstr "実行コマンド" #: tickets/models/ticket/command_confirm.py:14 +#, fuzzy msgid "Run account" -msgstr "" +msgstr "アカウント" #: tickets/models/ticket/command_confirm.py:21 msgid "From cmd filter" -msgstr "" +msgstr "コマンドフィルタ規則から" #: tickets/models/ticket/command_confirm.py:25 msgid "From cmd filter rule" -msgstr "" +msgstr "コマンドフィルタ規則から" #: tickets/models/ticket/general.py:74 msgid "Ticket step" -msgstr "" +msgstr "チケットステップ" #: tickets/models/ticket/general.py:92 msgid "Ticket assignee" -msgstr "" +msgstr "割り当てられたチケット" #: tickets/models/ticket/general.py:271 msgid "Title" -msgstr "" +msgstr "タイトル" #: tickets/models/ticket/general.py:287 msgid "Applicant" -msgstr "" +msgstr "応募者" #: tickets/models/ticket/general.py:291 msgid "TicketFlow" -msgstr "" +msgstr "作業指示プロセス" #: tickets/models/ticket/general.py:294 msgid "Approval step" -msgstr "" +msgstr "承認ステップ" #: tickets/models/ticket/general.py:297 msgid "Relation snapshot" -msgstr "" +msgstr "製造オーダスナップショット" #: tickets/models/ticket/general.py:391 msgid "Please try again" -msgstr "" +msgstr "もう一度お試しください" #: tickets/models/ticket/general.py:424 msgid "Super ticket" -msgstr "" +msgstr "スーパーチケット" #: tickets/models/ticket/login_asset_confirm.py:11 msgid "Login user" -msgstr "" +msgstr "ログインユーザー" #: tickets/models/ticket/login_asset_confirm.py:14 msgid "Login asset" -msgstr "" +msgstr "ログイン資産" #: tickets/models/ticket/login_asset_confirm.py:17 +#, fuzzy msgid "Login account" -msgstr "" +msgstr "ログインacl" #: tickets/models/ticket/login_confirm.py:12 msgid "Login datetime" -msgstr "" +msgstr "ログイン日時" #: tickets/notifications.py:63 msgid "Ticket basic info" -msgstr "" +msgstr "チケット基本情報" #: tickets/notifications.py:64 msgid "Ticket applied info" -msgstr "" +msgstr "チケット適用情報" #: tickets/notifications.py:109 msgid "Your has a new ticket, applicant - {}" -msgstr "" +msgstr "新しいチケットがあります- {}" #: tickets/notifications.py:113 msgid "{}: New Ticket - {} ({})" -msgstr "" +msgstr "新しいチケット- {} ({})" #: tickets/notifications.py:157 msgid "Your ticket has been processed, processor - {}" -msgstr "" +msgstr "チケットが処理されました。プロセッサー- {}" #: tickets/notifications.py:161 msgid "Ticket has processed - {} ({})" -msgstr "" +msgstr "チケットが処理済み- {} ({})" #: tickets/serializers/flow.py:21 msgid "Assignees display" -msgstr "" +msgstr "受付者名" #: tickets/serializers/flow.py:47 msgid "Please select the Assignees" -msgstr "" +msgstr "受付をお選びください" #: tickets/serializers/flow.py:75 msgid "The current organization type already exists" -msgstr "" +msgstr "現在の組織タイプは既に存在します。" #: tickets/serializers/super_ticket.py:11 msgid "Processor" -msgstr "" +msgstr "プロセッサ" #: tickets/serializers/ticket/apply_asset.py:20 +#, fuzzy msgid "Apply actions" -msgstr "" +msgstr "アプリケーションの適用" #: tickets/serializers/ticket/common.py:15 #: tickets/serializers/ticket/common.py:77 msgid "Created by ticket ({}-{})" -msgstr "" +msgstr "チケットで作成 ({}-{})" #: tickets/serializers/ticket/common.py:67 msgid "The expiration date should be greater than the start date" -msgstr "" +msgstr "有効期限は開始日より大きくする必要があります" #: tickets/serializers/ticket/common.py:84 msgid "Permission named `{}` already exists" -msgstr "" +msgstr "'{}'という名前の権限は既に存在します" #: tickets/serializers/ticket/ticket.py:96 msgid "The ticket flow `{}` does not exist" -msgstr "" +msgstr "チケットフロー '{}'が存在しない" #: tickets/templates/tickets/_msg_ticket.html:20 msgid "View details" -msgstr "" +msgstr "詳細の表示" #: tickets/templates/tickets/_msg_ticket.html:25 msgid "Direct approval" -msgstr "" +msgstr "直接承認" #: tickets/templates/tickets/approve_check_password.html:11 msgid "Ticket information" -msgstr "" +msgstr "作業指示情報" #: tickets/templates/tickets/approve_check_password.html:29 #: tickets/views/approve.py:38 msgid "Ticket approval" -msgstr "" +msgstr "作業指示の承認" #: tickets/templates/tickets/approve_check_password.html:45 msgid "Approval" -msgstr "" +msgstr "承認" #: tickets/templates/tickets/approve_check_password.html:54 msgid "Go Login" -msgstr "" +msgstr "ログイン" #: tickets/views/approve.py:39 msgid "" "This ticket does not exist, the process has ended, or this link has expired" msgstr "" +"このワークシートが存在しないか、ワークシートが終了したか、このリンクが無効に" +"なっています" #: tickets/views/approve.py:68 msgid "Click the button below to approve or reject" -msgstr "" +msgstr "下のボタンをクリックして同意または拒否。" #: tickets/views/approve.py:70 msgid "After successful authentication, this ticket can be approved directly" -msgstr "" +msgstr "認証に成功した後、作業指示書は直接承認することができる。" #: tickets/views/approve.py:92 msgid "Illegal approval action" -msgstr "" +msgstr "無効な承認アクション" #: tickets/views/approve.py:105 msgid "This user is not authorized to approve this ticket" -msgstr "" +msgstr "このユーザーはこの作業指示を承認する権限がありません" #: users/api/user.py:183 msgid "Could not reset self otp, use profile reset instead" -msgstr "" +msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用" #: users/apps.py:9 msgid "Users" -msgstr "" +msgstr "ユーザー" #: users/const.py:10 msgid "System administrator" -msgstr "" +msgstr "システム管理者" #: users/const.py:11 msgid "System auditor" -msgstr "" +msgstr "システム監査人" #: users/const.py:12 msgid "Organization administrator" -msgstr "" +msgstr "組織管理者" #: users/const.py:13 msgid "Organization auditor" -msgstr "" +msgstr "組織監査人" #: users/const.py:18 msgid "Reset link will be generated and sent to the user" -msgstr "" +msgstr "リセットリンクが生成され、ユーザーに送信されます" #: users/const.py:19 msgid "Set password" -msgstr "" +msgstr "パスワードの設定" #: users/exceptions.py:10 msgid "MFA not enabled" -msgstr "" +msgstr "MFAが有効化されていません" #: users/exceptions.py:20 msgid "MFA method not support" -msgstr "" +msgstr "MFAメソッドはサポートしていません" #: users/forms/profile.py:50 msgid "" @@ -5767,10 +6040,12 @@ msgid "" "in. you can also directly bind in \"personal information -> quick " "modification -> change MFA Settings\"!" msgstr "" +"有効にすると、次回のログイン時にマルチファクタ認証バインドプロセスに入りま" +"す。(個人情報->クイック修正->MFAマルチファクタ認証の設定)で直接バインド!" #: users/forms/profile.py:61 msgid "* Enable MFA to make the account more secure." -msgstr "" +msgstr "* アカウントをより安全にするためにMFAを有効にします。" #: users/forms/profile.py:70 msgid "" @@ -5778,710 +6053,726 @@ msgid "" "and key sensitive information properly. (for example: setting complex " "password, enabling MFA)" msgstr "" +"あなたとあなたの会社を保護するために、アカウント、パスワード、キーの機密情報" +"を適切に保管してください。(例: 複雑なパスワードの設定、MFAの有効化)" #: users/forms/profile.py:77 msgid "Finish" -msgstr "" +msgstr "仕上げ" #: users/forms/profile.py:84 msgid "New password" -msgstr "" +msgstr "新しいパスワード" #: users/forms/profile.py:89 msgid "Confirm password" -msgstr "" +msgstr "パスワードの確認" #: users/forms/profile.py:97 msgid "Password does not match" -msgstr "" +msgstr "パスワードが一致しない" #: users/forms/profile.py:118 msgid "Old password" -msgstr "" +msgstr "古いパスワード" #: users/forms/profile.py:128 msgid "Old password error" -msgstr "" +msgstr "古いパスワードエラー" #: users/forms/profile.py:138 msgid "Automatically configure and download the SSH key" -msgstr "" +msgstr "SSHキーの自動設定とダウンロード" #: users/forms/profile.py:140 msgid "ssh public key" -msgstr "" +msgstr "ssh公開キー" #: users/forms/profile.py:141 msgid "ssh-rsa AAAA..." -msgstr "" +msgstr "ssh-rsa AAAA.." #: users/forms/profile.py:142 msgid "Paste your id_rsa.pub here." -msgstr "" +msgstr "ここにid_rsa.pubを貼り付けます。" #: users/forms/profile.py:155 msgid "Public key should not be the same as your old one." -msgstr "" +msgstr "公開鍵は古いものと同じであってはなりません。" #: users/forms/profile.py:159 users/serializers/profile.py:100 #: users/serializers/profile.py:183 users/serializers/profile.py:210 msgid "Not a valid ssh public key" -msgstr "" +msgstr "有効なssh公開鍵ではありません" #: users/forms/profile.py:170 users/models/user.py:708 msgid "Public key" -msgstr "" +msgstr "公開キー" #: users/models/user.py:561 msgid "Force enable" -msgstr "" +msgstr "強制有効" #: users/models/user.py:631 msgid "Local" -msgstr "" +msgstr "ローカル" #: users/models/user.py:687 users/serializers/user.py:204 msgid "Is service account" -msgstr "" +msgstr "サービスアカウントです" #: users/models/user.py:689 msgid "Avatar" -msgstr "" +msgstr "アバター" #: users/models/user.py:692 msgid "Wechat" -msgstr "" +msgstr "微信" #: users/models/user.py:695 msgid "Phone" -msgstr "" +msgstr "電話" #: users/models/user.py:701 msgid "OTP secret key" -msgstr "" +msgstr "OTP 秘密" #: users/models/user.py:705 msgid "Private key" -msgstr "" +msgstr "ssh秘密鍵" #: users/models/user.py:711 msgid "Secret key" -msgstr "" +msgstr "秘密キー" #: users/models/user.py:716 users/serializers/profile.py:149 #: users/serializers/user.py:201 msgid "Is first login" -msgstr "" +msgstr "最初のログインです" #: users/models/user.py:727 msgid "Source" -msgstr "" +msgstr "ソース" #: users/models/user.py:731 msgid "Date password last updated" -msgstr "" +msgstr "最終更新日パスワード" #: users/models/user.py:734 msgid "Need update password" -msgstr "" +msgstr "更新パスワードが必要" #: users/models/user.py:909 msgid "Can invite user" -msgstr "" +msgstr "ユーザーを招待できます" #: users/models/user.py:910 msgid "Can remove user" -msgstr "" +msgstr "ユーザーを削除できます" #: users/models/user.py:911 msgid "Can match user" -msgstr "" +msgstr "ユーザーに一致できます" #: users/models/user.py:920 msgid "Administrator" -msgstr "" +msgstr "管理者" #: users/models/user.py:923 msgid "Administrator is the super user of system" -msgstr "" +msgstr "管理者はシステムのスーパーユーザーです" #: users/models/user.py:948 msgid "User password history" -msgstr "" +msgstr "ユーザーパスワード履歴" #: users/notifications.py:55 #: users/templates/users/_msg_password_expire_reminder.html:17 #: users/templates/users/reset_password.html:5 #: users/templates/users/reset_password.html:6 msgid "Reset password" -msgstr "" +msgstr "パスワードのリセット" #: users/notifications.py:85 users/views/profile/reset.py:194 msgid "Reset password success" -msgstr "" +msgstr "パスワードのリセット成功" #: users/notifications.py:117 msgid "Reset public key success" -msgstr "" +msgstr "公開鍵のリセット成功" #: users/notifications.py:143 msgid "Password is about expire" -msgstr "" +msgstr "パスワードの有効期限が近づいています" #: users/notifications.py:171 msgid "Account is about expire" -msgstr "" +msgstr "アカウントの有効期限が近づいています" #: users/notifications.py:193 msgid "Reset SSH Key" -msgstr "" +msgstr "SSHキーのリセット" #: users/notifications.py:214 msgid "Reset MFA" -msgstr "" +msgstr "MFAのリセット" #: users/serializers/profile.py:30 msgid "The old password is incorrect" -msgstr "" +msgstr "古いパスワードが正しくありません" #: users/serializers/profile.py:37 users/serializers/profile.py:197 msgid "Password does not match security rules" -msgstr "" +msgstr "パスワードがセキュリティルールと一致しない" #: users/serializers/profile.py:41 msgid "The new password cannot be the last {} passwords" -msgstr "" +msgstr "新しいパスワードを最後の {} 個のパスワードにすることはできません" #: users/serializers/profile.py:49 users/serializers/profile.py:71 msgid "The newly set password is inconsistent" -msgstr "" +msgstr "新しく設定されたパスワードが一致しない" #: users/serializers/user.py:30 msgid "System roles" -msgstr "" +msgstr "システムの役割" #: users/serializers/user.py:35 msgid "Org roles" -msgstr "" +msgstr "組織ロール" #: users/serializers/user.py:38 msgid "System roles display" -msgstr "" +msgstr "システムロール表示" #: users/serializers/user.py:40 msgid "Org roles display" -msgstr "" +msgstr "組織ロール表示" #: users/serializers/user.py:90 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" -msgstr "" +msgstr "パスワード戦略" #: users/serializers/user.py:92 msgid "MFA enabled" -msgstr "" +msgstr "MFA有効化" #: users/serializers/user.py:94 msgid "MFA force enabled" -msgstr "" +msgstr "MFAフォース有効化" #: users/serializers/user.py:97 msgid "MFA level display" -msgstr "" +msgstr "MFAレベル表示" #: users/serializers/user.py:99 msgid "Login blocked" -msgstr "" +msgstr "ログインブロック" #: users/serializers/user.py:102 msgid "Can public key authentication" -msgstr "" +msgstr "公開鍵認証が可能" #: users/serializers/user.py:206 msgid "Avatar url" -msgstr "" +msgstr "アバターURL" #: users/serializers/user.py:208 msgid "Groups name" -msgstr "" +msgstr "グループ名" #: users/serializers/user.py:209 msgid "Source name" -msgstr "" +msgstr "ソース名" #: users/serializers/user.py:210 msgid "Organization role name" -msgstr "" +msgstr "組織の役割名" #: users/serializers/user.py:211 msgid "Super role name" -msgstr "" +msgstr "スーパーロール名" #: users/serializers/user.py:212 msgid "Total role name" -msgstr "" +msgstr "合計ロール名" #: users/serializers/user.py:214 msgid "Is wecom bound" -msgstr "" +msgstr "企業の微信をバインドしているかどうか" #: users/serializers/user.py:215 msgid "Is dingtalk bound" -msgstr "" +msgstr "ピンをバインドしているかどうか" #: users/serializers/user.py:216 msgid "Is feishu bound" -msgstr "" +msgstr "飛本を縛ったかどうか" #: users/serializers/user.py:217 msgid "Is OTP bound" -msgstr "" +msgstr "仮想MFAがバインドされているか" #: users/serializers/user.py:219 msgid "System role name" -msgstr "" +msgstr "システムロール名" #: users/serializers/user.py:325 msgid "Select users" -msgstr "" +msgstr "ユーザーの選択" #: users/serializers/user.py:326 msgid "For security, only list several users" -msgstr "" +msgstr "セキュリティのために、複数のユーザーのみをリストします" #: users/serializers/user.py:362 msgid "name not unique" -msgstr "" +msgstr "名前が一意ではない" #: users/templates/users/_msg_account_expire_reminder.html:7 msgid "Your account will expire in" -msgstr "" +msgstr "アカウントの有効期限は" #: users/templates/users/_msg_account_expire_reminder.html:8 msgid "" "In order not to affect your normal work, please contact the administrator " "for confirmation." msgstr "" +"通常の作業に影響を与えないように、確認のために管理者に連絡してください。" #: users/templates/users/_msg_password_expire_reminder.html:7 msgid "Your password will expire in" -msgstr "" +msgstr "パスワードは" #: users/templates/users/_msg_password_expire_reminder.html:8 msgid "" "For your account security, please click on the link below to update your " "password in time" msgstr "" +"アカウントのセキュリティについては、下のリンクをクリックしてパスワードを時間" +"内に更新してください" #: users/templates/users/_msg_password_expire_reminder.html:11 msgid "Click here update password" -msgstr "" +msgstr "ここをクリック更新パスワード" #: users/templates/users/_msg_password_expire_reminder.html:16 msgid "If your password has expired, please click the link below to" msgstr "" +"パスワードの有効期限が切れている場合は、以下のリンクをクリックしてください" #: users/templates/users/_msg_reset_mfa.html:7 msgid "Your MFA has been reset by site administrator" -msgstr "" +msgstr "MFAはサイト管理者によってリセットされました" #: users/templates/users/_msg_reset_mfa.html:8 #: users/templates/users/_msg_reset_ssh_key.html:8 msgid "Please click the link below to set" -msgstr "" +msgstr "以下のリンクをクリックして設定してください" #: users/templates/users/_msg_reset_mfa.html:11 #: users/templates/users/_msg_reset_ssh_key.html:11 msgid "Click here set" -msgstr "" +msgstr "ここをクリックセット" #: users/templates/users/_msg_reset_ssh_key.html:7 msgid "Your ssh public key has been reset by site administrator" -msgstr "" +msgstr "あなたのssh公開鍵はサイト管理者によってリセットされました" #: users/templates/users/_msg_user_created.html:15 msgid "click here to set your password" -msgstr "" +msgstr "ここをクリックしてパスワードを設定してください" #: users/templates/users/forgot_password.html:32 msgid "Input your email account, that will send a email to your" -msgstr "" +msgstr "あなたのメールを入力し、それはあなたにメールを送信します" #: users/templates/users/forgot_password.html:35 msgid "" "Enter your mobile number and a verification code will be sent to your phone" -msgstr "" +msgstr "携帯電話番号を入力すると、認証コードが携帯電話に送信されます" #: users/templates/users/forgot_password.html:57 msgid "Email account" -msgstr "" +msgstr "メールアドレス" #: users/templates/users/forgot_password.html:61 msgid "Mobile number" -msgstr "" +msgstr "携帯番号" #: users/templates/users/forgot_password.html:68 msgid "Send" -msgstr "" +msgstr "送信" #: users/templates/users/forgot_password.html:72 #: users/templates/users/forgot_password_previewing.html:30 msgid "Submit" -msgstr "" +msgstr "送信" #: users/templates/users/forgot_password_previewing.html:21 msgid "Please enter the username for which you want to retrieve the password" -msgstr "" +msgstr "パスワードを取り戻す必要があるユーザー名を入力してください" #: users/templates/users/mfa_setting.html:24 msgid "Enable MFA" -msgstr "" +msgstr "MFAの有効化" #: users/templates/users/mfa_setting.html:30 msgid "MFA force enable, cannot disable" -msgstr "" +msgstr "MFA強制有効化、無効化できません" #: users/templates/users/mfa_setting.html:48 msgid "MFA setting" -msgstr "" +msgstr "MFAの設定" #: users/templates/users/reset_password.html:23 msgid "Your password must satisfy" -msgstr "" +msgstr "パスワードを満たす必要があります" #: users/templates/users/reset_password.html:24 msgid "Password strength" -msgstr "" +msgstr "パスワードの強さ" #: users/templates/users/reset_password.html:48 msgid "Very weak" -msgstr "" +msgstr "非常に弱い" #: users/templates/users/reset_password.html:49 msgid "Weak" -msgstr "" +msgstr "弱い" #: users/templates/users/reset_password.html:51 msgid "Medium" -msgstr "" +msgstr "中" #: users/templates/users/reset_password.html:52 msgid "Strong" -msgstr "" +msgstr "強い" #: users/templates/users/reset_password.html:53 msgid "Very strong" -msgstr "" +msgstr "非常に強い" #: users/templates/users/user_otp_check_password.html:6 msgid "Enable OTP" -msgstr "" +msgstr "OTPの有効化" #: users/templates/users/user_otp_enable_bind.html:6 msgid "Bind one-time password authenticator" -msgstr "" +msgstr "ワンタイムパスワード認証子のバインド" #: users/templates/users/user_otp_enable_bind.html:13 msgid "" "Use the MFA Authenticator application to scan the following qr code for a 6-" "bit verification code" msgstr "" +"MFA Authenticatorアプリケーションを使用して、次のqrコードを6ビット検証コード" +"でスキャンします。" #: users/templates/users/user_otp_enable_bind.html:22 #: users/templates/users/user_verify_mfa.html:27 msgid "Six figures" -msgstr "" +msgstr "6つの数字" #: users/templates/users/user_otp_enable_install_app.html:6 msgid "Install app" -msgstr "" +msgstr "アプリのインストール" #: users/templates/users/user_otp_enable_install_app.html:13 msgid "" "Download and install the MFA Authenticator application on your phone or " "applet of WeChat" msgstr "" +"携帯電話またはWeChatのアプレットにMFA Authenticatorアプリケーションをダウン" +"ロードしてインストールします" #: users/templates/users/user_otp_enable_install_app.html:18 msgid "Android downloads" -msgstr "" +msgstr "Androidのダウンロード" #: users/templates/users/user_otp_enable_install_app.html:23 msgid "iPhone downloads" -msgstr "" +msgstr "IPhoneのダウンロード" #: users/templates/users/user_otp_enable_install_app.html:26 msgid "" "After installation, click the next step to enter the binding page (if " "installed, go to the next step directly)." msgstr "" +"インストール後、次のステップをクリックしてバインディングページに入ります (イ" +"ンストールされている場合は、次のステップに直接進みます)。" #: users/templates/users/user_password_verify.html:8 #: users/templates/users/user_password_verify.html:9 msgid "Verify password" -msgstr "" +msgstr "パスワードの確認" #: users/templates/users/user_verify_mfa.html:9 msgid "Authenticate" -msgstr "" +msgstr "認証" #: users/templates/users/user_verify_mfa.html:15 msgid "" "The account protection has been opened, please complete the following " "operations according to the prompts" msgstr "" +"アカウント保護が開始されました。プロンプトに従って次の操作を完了してください" #: users/templates/users/user_verify_mfa.html:17 msgid "Open MFA Authenticator and enter the 6-bit dynamic code" -msgstr "" +msgstr "MFA Authenticatorを開き、6ビットの動的コードを入力します" #: users/views/profile/otp.py:87 msgid "Already bound" -msgstr "" +msgstr "すでにバインド済み" #: users/views/profile/otp.py:88 msgid "MFA already bound, disable first, then bound" msgstr "" +"MFAはすでにバインドされており、最初に無効にしてからバインドされています。" #: users/views/profile/otp.py:115 msgid "OTP enable success" -msgstr "" +msgstr "OTP有効化成功" #: users/views/profile/otp.py:116 msgid "OTP enable success, return login page" -msgstr "" +msgstr "OTP有効化成功、ログインページを返す" #: users/views/profile/otp.py:158 msgid "Disable OTP" -msgstr "" +msgstr "OTPの無効化" #: users/views/profile/otp.py:164 msgid "OTP disable success" -msgstr "" +msgstr "OTP無効化成功" #: users/views/profile/otp.py:165 msgid "OTP disable success, return login page" -msgstr "" +msgstr "OTP無効化成功、ログインページを返す" #: users/views/profile/password.py:36 users/views/profile/password.py:41 msgid "Password invalid" -msgstr "" +msgstr "パスワード無効" #: users/views/profile/reset.py:47 msgid "" "Non-local users can log in only from third-party platforms and cannot change " "their passwords: {}" msgstr "" +"ローカル以外のユーザーは、サードパーティ プラットフォームからのログインのみが" +"許可され、パスワードの変更はサポートされていません: {}" #: users/views/profile/reset.py:149 users/views/profile/reset.py:160 msgid "Token invalid or expired" -msgstr "" +msgstr "トークンが無効または期限切れ" #: users/views/profile/reset.py:165 msgid "User auth from {}, go there change password" -msgstr "" +msgstr "ユーザー認証ソース {}, 対応するシステムにパスワードを変更してください" #: users/views/profile/reset.py:172 msgid "* Your password does not meet the requirements" -msgstr "" +msgstr "* パスワードが要件を満たしていない" #: users/views/profile/reset.py:178 msgid "* The new password cannot be the last {} passwords" -msgstr "" +msgstr "* 新しいパスワードを最後の {} パスワードにすることはできません" #: users/views/profile/reset.py:195 msgid "Reset password success, return to login page" -msgstr "" +msgstr "パスワードの成功をリセットし、ログインページに戻る" #: xpack/apps.py:8 msgid "XPACK" -msgstr "" +msgstr "XPack" #: xpack/plugins/change_auth_plan/meta.py:9 #: xpack/plugins/change_auth_plan/models/asset.py:124 msgid "Change auth plan" -msgstr "" +msgstr "密かな計画" #: xpack/plugins/change_auth_plan/models/app.py:45 #: xpack/plugins/change_auth_plan/models/app.py:94 msgid "Application change auth plan" -msgstr "" +msgstr "改密計画の適用" #: xpack/plugins/change_auth_plan/models/app.py:98 #: xpack/plugins/change_auth_plan/models/app.py:150 msgid "Application change auth plan execution" -msgstr "" +msgstr "改密計画実行の適用" #: xpack/plugins/change_auth_plan/models/app.py:143 msgid "App" -msgstr "" +msgstr "適用" #: xpack/plugins/change_auth_plan/models/app.py:155 msgid "Application change auth plan task" -msgstr "" +msgstr "改密計画タスクの適用" #: xpack/plugins/change_auth_plan/models/app.py:179 #: xpack/plugins/change_auth_plan/models/asset.py:264 msgid "Password cannot be set to blank, exit. " -msgstr "" +msgstr "パスワードを空白に設定することはできません。" #: xpack/plugins/change_auth_plan/models/asset.py:68 msgid "Asset change auth plan" -msgstr "" +msgstr "資産変更のオースプラン" #: xpack/plugins/change_auth_plan/models/asset.py:135 msgid "Asset change auth plan execution" -msgstr "" +msgstr "資産変更のオースプランの実行" #: xpack/plugins/change_auth_plan/models/asset.py:211 msgid "Change auth plan execution" -msgstr "" +msgstr "改密計画の実行" #: xpack/plugins/change_auth_plan/models/asset.py:218 msgid "Asset change auth plan task" -msgstr "" +msgstr "資産改密計画タスク" #: xpack/plugins/change_auth_plan/models/asset.py:253 msgid "This asset does not have a privileged user set: " -msgstr "" +msgstr "このアセットには特権ユーザーセットがありません。" #: xpack/plugins/change_auth_plan/models/asset.py:259 msgid "" "The password and key of the current asset privileged user cannot be changed: " -msgstr "" +msgstr "現在のアセット特権ユーザーのパスワードとキーは変更できません。" #: xpack/plugins/change_auth_plan/models/asset.py:270 msgid "Public key cannot be set to null, exit. " -msgstr "" +msgstr "公開鍵をnull、exitに設定することはできません。" #: xpack/plugins/change_auth_plan/models/base.py:114 msgid "Change auth plan snapshot" -msgstr "" +msgstr "計画スナップショットの暗号化" #: xpack/plugins/change_auth_plan/models/base.py:184 msgid "Preflight check" -msgstr "" +msgstr "プリフライトチェック" #: xpack/plugins/change_auth_plan/models/base.py:185 msgid "Change auth" -msgstr "" +msgstr "秘密を改める" #: xpack/plugins/change_auth_plan/models/base.py:186 msgid "Verify auth" -msgstr "" +msgstr "パスワード/キーの確認" #: xpack/plugins/change_auth_plan/models/base.py:187 msgid "Keep auth" -msgstr "" +msgstr "パスワード/キーの保存" #: xpack/plugins/change_auth_plan/models/base.py:195 msgid "Step" -msgstr "" +msgstr "ステップ" #: xpack/plugins/change_auth_plan/serializers/asset.py:30 msgid "Change Password" -msgstr "" +msgstr "パスワードの変更" #: xpack/plugins/change_auth_plan/serializers/asset.py:31 msgid "Change SSH Key" -msgstr "" +msgstr "SSHキーの変更" #: xpack/plugins/change_auth_plan/serializers/base.py:44 msgid "Run times" -msgstr "" +msgstr "実行時間" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 msgid "After many attempts to change the secret, it still failed" -msgstr "" +msgstr "秘密を変更しようとする多くの試みの後、それはまだ失敗しました" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:255 msgid "Invalid/incorrect password" -msgstr "" +msgstr "パスワードが無効/間違っている" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:257 msgid "Failed to connect to the host" -msgstr "" +msgstr "ホストへの接続に失敗しました" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:259 msgid "Data could not be sent to remote" -msgstr "" +msgstr "データをリモートに送信できませんでした" #: xpack/plugins/cloud/api.py:40 msgid "Test connection successful" -msgstr "" +msgstr "テスト接続成功" #: xpack/plugins/cloud/api.py:42 msgid "Test connection failed: {}" -msgstr "" +msgstr "テスト接続に失敗しました: {}" #: xpack/plugins/cloud/const.py:8 msgid "Alibaba Cloud" -msgstr "" +msgstr "アリ雲" #: xpack/plugins/cloud/const.py:9 msgid "AWS (International)" -msgstr "" +msgstr "AWS (国際)" #: xpack/plugins/cloud/const.py:10 msgid "AWS (China)" -msgstr "" +msgstr "AWS (中国)" #: xpack/plugins/cloud/const.py:11 msgid "Azure (China)" -msgstr "" +msgstr "Azure (中国)" #: xpack/plugins/cloud/const.py:12 msgid "Azure (International)" -msgstr "" +msgstr "Azure (国際)" #: xpack/plugins/cloud/const.py:14 msgid "Baidu Cloud" -msgstr "" +msgstr "百度雲" #: xpack/plugins/cloud/const.py:15 msgid "JD Cloud" -msgstr "" +msgstr "京東雲" #: xpack/plugins/cloud/const.py:16 msgid "KingSoft Cloud" -msgstr "" +msgstr "金山雲" #: xpack/plugins/cloud/const.py:17 msgid "Tencent Cloud" -msgstr "" +msgstr "テンセント雲" #: xpack/plugins/cloud/const.py:18 msgid "Tencent Cloud (Lighthouse)" -msgstr "" +msgstr "テンセント雲(軽量アプリケーション)" #: xpack/plugins/cloud/const.py:19 msgid "VMware" -msgstr "" +msgstr "VMware" #: xpack/plugins/cloud/const.py:20 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" -msgstr "" +msgstr "Nutanix" #: xpack/plugins/cloud/const.py:21 msgid "Huawei Private Cloud" -msgstr "" +msgstr "華為私有雲" #: xpack/plugins/cloud/const.py:22 msgid "Qingyun Private Cloud" -msgstr "" +msgstr "青雲私有雲" #: xpack/plugins/cloud/const.py:23 msgid "CTYun Private Cloud" -msgstr "" +msgstr "スカイウィング私有雲" #: xpack/plugins/cloud/const.py:24 msgid "OpenStack" -msgstr "" +msgstr "OpenStack" #: xpack/plugins/cloud/const.py:25 msgid "Google Cloud Platform" -msgstr "" +msgstr "谷歌雲" #: xpack/plugins/cloud/const.py:26 msgid "Fusion Compute" @@ -6489,374 +6780,374 @@ msgstr "" #: xpack/plugins/cloud/const.py:31 msgid "Private IP" -msgstr "" +msgstr "プライベートIP" #: xpack/plugins/cloud/const.py:32 msgid "Public IP" -msgstr "" +msgstr "パブリックIP" #: xpack/plugins/cloud/const.py:36 msgid "Instance name" -msgstr "" +msgstr "インスタンス名" #: xpack/plugins/cloud/const.py:37 msgid "Instance name and Partial IP" -msgstr "" +msgstr "インスタンス名と部分IP" #: xpack/plugins/cloud/const.py:42 msgid "Succeed" -msgstr "" +msgstr "成功" #: xpack/plugins/cloud/const.py:46 msgid "Unsync" -msgstr "" +msgstr "同期していません" #: xpack/plugins/cloud/const.py:47 msgid "New Sync" -msgstr "" +msgstr "新しい同期" #: xpack/plugins/cloud/const.py:48 msgid "Synced" -msgstr "" +msgstr "同期済み" #: xpack/plugins/cloud/const.py:49 msgid "Released" -msgstr "" +msgstr "リリース済み" #: xpack/plugins/cloud/meta.py:9 msgid "Cloud center" -msgstr "" +msgstr "クラウドセンター" #: xpack/plugins/cloud/models.py:32 msgid "Provider" -msgstr "" +msgstr "プロバイダー" #: xpack/plugins/cloud/models.py:36 msgid "Validity" -msgstr "" +msgstr "有効性" #: xpack/plugins/cloud/models.py:41 msgid "Cloud account" -msgstr "" +msgstr "クラウドアカウント" #: xpack/plugins/cloud/models.py:43 msgid "Test cloud account" -msgstr "" +msgstr "クラウドアカウントのテスト" #: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:38 msgid "Regions" -msgstr "" +msgstr "リージョン" #: xpack/plugins/cloud/models.py:93 msgid "Hostname strategy" -msgstr "" +msgstr "ホスト名戦略" #: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:72 msgid "Unix admin user" -msgstr "" +msgstr "Unix adminユーザー" #: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:73 msgid "Windows admin user" -msgstr "" +msgstr "Windows管理者" #: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:46 msgid "IP network segment group" -msgstr "" +msgstr "IPネットワークセグメントグループ" #: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:51 msgid "Sync IP type" -msgstr "" +msgstr "同期IPタイプ" #: xpack/plugins/cloud/models.py:118 xpack/plugins/cloud/serializers/task.py:76 msgid "Always update" -msgstr "" +msgstr "常に更新" #: xpack/plugins/cloud/models.py:124 msgid "Date last sync" -msgstr "" +msgstr "最終同期日" #: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 msgid "Sync instance task" -msgstr "" +msgstr "インスタンスの同期タスク" #: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 msgid "Date sync" -msgstr "" +msgstr "日付の同期" #: xpack/plugins/cloud/models.py:185 msgid "Sync instance task execution" -msgstr "" +msgstr "インスタンスタスクの同期実行" #: xpack/plugins/cloud/models.py:209 msgid "Sync task" -msgstr "" +msgstr "同期タスク" #: xpack/plugins/cloud/models.py:213 msgid "Sync instance task history" -msgstr "" +msgstr "インスタンスタスク履歴の同期" #: xpack/plugins/cloud/models.py:216 msgid "Instance" -msgstr "" +msgstr "インスタンス" #: xpack/plugins/cloud/models.py:233 msgid "Sync instance detail" -msgstr "" +msgstr "同期インスタンスの詳細" #: xpack/plugins/cloud/providers/aws_international.py:17 msgid "China (Beijing)" -msgstr "" +msgstr "中国 (北京)" #: xpack/plugins/cloud/providers/aws_international.py:18 msgid "China (Ningxia)" -msgstr "" +msgstr "中国 (寧夏)" #: xpack/plugins/cloud/providers/aws_international.py:21 msgid "US East (Ohio)" -msgstr "" +msgstr "米国東部 (オハイオ州)" #: xpack/plugins/cloud/providers/aws_international.py:22 msgid "US East (N. Virginia)" -msgstr "" +msgstr "米国東部 (N. バージニア州)" #: xpack/plugins/cloud/providers/aws_international.py:23 msgid "US West (N. California)" -msgstr "" +msgstr "米国西部 (N. カリフォルニア州)" #: xpack/plugins/cloud/providers/aws_international.py:24 msgid "US West (Oregon)" -msgstr "" +msgstr "米国西部 (オレゴン州)" #: xpack/plugins/cloud/providers/aws_international.py:25 msgid "Africa (Cape Town)" -msgstr "" +msgstr "アフリカ (ケープタウン)" #: xpack/plugins/cloud/providers/aws_international.py:26 msgid "Asia Pacific (Hong Kong)" -msgstr "" +msgstr "アジアパシフィック (香港)" #: xpack/plugins/cloud/providers/aws_international.py:27 msgid "Asia Pacific (Mumbai)" -msgstr "" +msgstr "アジア太平洋 (ムンバイ)" #: xpack/plugins/cloud/providers/aws_international.py:28 msgid "Asia Pacific (Osaka-Local)" -msgstr "" +msgstr "アジアパシフィック (大阪-ローカル)" #: xpack/plugins/cloud/providers/aws_international.py:29 msgid "Asia Pacific (Seoul)" -msgstr "" +msgstr "アジア太平洋地域 (ソウル)" #: xpack/plugins/cloud/providers/aws_international.py:30 msgid "Asia Pacific (Singapore)" -msgstr "" +msgstr "アジア太平洋 (シンガポール)" #: xpack/plugins/cloud/providers/aws_international.py:31 msgid "Asia Pacific (Sydney)" -msgstr "" +msgstr "アジア太平洋 (シドニー)" #: xpack/plugins/cloud/providers/aws_international.py:32 msgid "Asia Pacific (Tokyo)" -msgstr "" +msgstr "アジアパシフィック (東京)" #: xpack/plugins/cloud/providers/aws_international.py:33 msgid "Canada (Central)" -msgstr "" +msgstr "カナダ (中央)" #: xpack/plugins/cloud/providers/aws_international.py:34 msgid "Europe (Frankfurt)" -msgstr "" +msgstr "ヨーロッパ (フランクフルト)" #: xpack/plugins/cloud/providers/aws_international.py:35 msgid "Europe (Ireland)" -msgstr "" +msgstr "ヨーロッパ (アイルランド)" #: xpack/plugins/cloud/providers/aws_international.py:36 msgid "Europe (London)" -msgstr "" +msgstr "ヨーロッパ (ロンドン)" #: xpack/plugins/cloud/providers/aws_international.py:37 msgid "Europe (Milan)" -msgstr "" +msgstr "ヨーロッパ (ミラノ)" #: xpack/plugins/cloud/providers/aws_international.py:38 msgid "Europe (Paris)" -msgstr "" +msgstr "ヨーロッパ (パリ)" #: xpack/plugins/cloud/providers/aws_international.py:39 msgid "Europe (Stockholm)" -msgstr "" +msgstr "ヨーロッパ (ストックホルム)" #: xpack/plugins/cloud/providers/aws_international.py:40 msgid "Middle East (Bahrain)" -msgstr "" +msgstr "中东 (バーレーン)" #: xpack/plugins/cloud/providers/aws_international.py:41 msgid "South America (São Paulo)" -msgstr "" +msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 #: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" -msgstr "" +msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 #: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" -msgstr "" +msgstr "華南-広州" #: xpack/plugins/cloud/providers/baiducloud.py:56 msgid "CN East-Suzhou" -msgstr "" +msgstr "華東-蘇州" #: xpack/plugins/cloud/providers/baiducloud.py:57 #: xpack/plugins/cloud/providers/huaweicloud.py:48 msgid "CN-Hong Kong" -msgstr "" +msgstr "中国-香港" #: xpack/plugins/cloud/providers/baiducloud.py:58 msgid "CN Center-Wuhan" -msgstr "" +msgstr "華中-武漢" #: xpack/plugins/cloud/providers/baiducloud.py:59 msgid "CN North-Baoding" -msgstr "" +msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 #: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" -msgstr "" +msgstr "華東-上海" #: xpack/plugins/cloud/providers/baiducloud.py:61 #: xpack/plugins/cloud/providers/huaweicloud.py:47 msgid "AP-Singapore" -msgstr "" +msgstr "アジア太平洋-シンガポール" #: xpack/plugins/cloud/providers/huaweicloud.py:35 msgid "AF-Johannesburg" -msgstr "" +msgstr "アフリカ-ヨハネスブルク" #: xpack/plugins/cloud/providers/huaweicloud.py:36 msgid "CN North-Beijing4" -msgstr "" +msgstr "華北-北京4" #: xpack/plugins/cloud/providers/huaweicloud.py:37 msgid "CN North-Beijing1" -msgstr "" +msgstr "華北-北京1" #: xpack/plugins/cloud/providers/huaweicloud.py:38 msgid "CN East-Shanghai2" -msgstr "" +msgstr "華東-上海2" #: xpack/plugins/cloud/providers/huaweicloud.py:39 msgid "CN East-Shanghai1" -msgstr "" +msgstr "華東-上海1" #: xpack/plugins/cloud/providers/huaweicloud.py:41 msgid "LA-Mexico City1" -msgstr "" +msgstr "LA-メキシコCity1" #: xpack/plugins/cloud/providers/huaweicloud.py:42 msgid "LA-Santiago" -msgstr "" +msgstr "ラテンアメリカ-サンディエゴ" #: xpack/plugins/cloud/providers/huaweicloud.py:43 msgid "LA-Sao Paulo1" -msgstr "" +msgstr "ラミー・サンパウロ1" #: xpack/plugins/cloud/providers/huaweicloud.py:44 msgid "EU-Paris" -msgstr "" +msgstr "ヨーロッパ-パリ" #: xpack/plugins/cloud/providers/huaweicloud.py:45 msgid "CN Southwest-Guiyang1" -msgstr "" +msgstr "南西-貴陽1" #: xpack/plugins/cloud/providers/huaweicloud.py:46 msgid "AP-Bangkok" -msgstr "" +msgstr "アジア太平洋-バンコク" #: xpack/plugins/cloud/providers/huaweicloud.py:50 msgid "CN Northeast-Dalian" -msgstr "" +msgstr "华北-大连" #: xpack/plugins/cloud/providers/huaweicloud.py:51 msgid "CN North-Ulanqab1" -msgstr "" +msgstr "華北-ウランチャブ一" #: xpack/plugins/cloud/providers/huaweicloud.py:52 msgid "CN South-Guangzhou-InvitationOnly" -msgstr "" +msgstr "華南-広州-友好ユーザー環境" #: xpack/plugins/cloud/providers/jdcloud.py:128 msgid "CN East-Suqian" -msgstr "" +msgstr "華東-宿遷" #: xpack/plugins/cloud/serializers/account.py:65 msgid "Validity display" -msgstr "" +msgstr "有効表示" #: xpack/plugins/cloud/serializers/account.py:66 msgid "Provider display" -msgstr "" +msgstr "プロバイダ表示" #: xpack/plugins/cloud/serializers/account_attrs.py:35 msgid "Client ID" -msgstr "" +msgstr "クライアントID" #: xpack/plugins/cloud/serializers/account_attrs.py:41 msgid "Tenant ID" -msgstr "" +msgstr "テナントID" #: xpack/plugins/cloud/serializers/account_attrs.py:44 msgid "Subscription ID" -msgstr "" +msgstr "サブスクリプションID" #: xpack/plugins/cloud/serializers/account_attrs.py:95 #: xpack/plugins/cloud/serializers/account_attrs.py:100 #: xpack/plugins/cloud/serializers/account_attrs.py:116 #: xpack/plugins/cloud/serializers/account_attrs.py:141 msgid "API Endpoint" -msgstr "" +msgstr "APIエンドポイント" #: xpack/plugins/cloud/serializers/account_attrs.py:106 msgid "Auth url" -msgstr "" +msgstr "認証アドレス" #: xpack/plugins/cloud/serializers/account_attrs.py:107 msgid "eg: http://openstack.example.com:5000/v3" -msgstr "" +msgstr "例えば: http://openstack.example.com:5000/v3" #: xpack/plugins/cloud/serializers/account_attrs.py:110 msgid "User domain" -msgstr "" +msgstr "ユーザードメイン" #: xpack/plugins/cloud/serializers/account_attrs.py:117 msgid "Cert File" -msgstr "" +msgstr "証明書ファイル" #: xpack/plugins/cloud/serializers/account_attrs.py:118 msgid "Key File" -msgstr "" +msgstr "キーファイル" #: xpack/plugins/cloud/serializers/account_attrs.py:134 msgid "Service account key" -msgstr "" +msgstr "サービスアカウントキー" #: xpack/plugins/cloud/serializers/account_attrs.py:135 msgid "The file is in JSON format" -msgstr "" +msgstr "ファイルはJSON形式です。" #: xpack/plugins/cloud/serializers/account_attrs.py:148 msgid "IP address invalid `{}`, {}" -msgstr "" +msgstr "IPアドレスが無効: '{}', {}" #: xpack/plugins/cloud/serializers/account_attrs.py:154 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" -msgstr "" +msgstr "形式はコンマ区切りの文字列です,例:192.168.1.0/24,10.0.0.0-10.0.0.255" #: xpack/plugins/cloud/serializers/account_attrs.py:158 msgid "" @@ -6864,22 +7155,25 @@ msgid "" "synchronization task is executed, only the valid IP address will be " "synchronized.
If the port is 0, all IP addresses are valid." msgstr "" +"このポートは、 IP アドレスの有効性を検出するために使用されます。同期タスクが" +"実行されると、有効な IP アドレスのみが同期されます。
ポートが0の場合、す" +"べてのIPアドレスが有効です。" #: xpack/plugins/cloud/serializers/account_attrs.py:166 msgid "Hostname prefix" -msgstr "" +msgstr "ホスト名プレフィックス" #: xpack/plugins/cloud/serializers/account_attrs.py:169 msgid "IP segment" -msgstr "" +msgstr "IP セグメント" #: xpack/plugins/cloud/serializers/account_attrs.py:173 msgid "Test port" -msgstr "" +msgstr "テストポート" #: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "Test timeout" -msgstr "" +msgstr "テストタイムアウト" #: xpack/plugins/cloud/serializers/task.py:29 msgid "" @@ -6889,108 +7183,564 @@ msgid "" "all instances and randomly match IP addresses.
Format for comma-" "delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" msgstr "" +"IP範囲に一致するインスタンスのみが同期されます。
インスタンスに複数のIPア" +"ドレスが含まれている場合、一致する最初のIPアドレスが作成されたアセットのIPと" +"して使用されます。
デフォルト値の*は、すべてのインスタンスを同期し、IPア" +"ドレスをランダムに一致させることを意味します。
形式はコンマ区切りの文字列" +"です。例:192.168.1.0/24,10.1.1.1-10.1.1.20" #: xpack/plugins/cloud/serializers/task.py:36 msgid "History count" -msgstr "" +msgstr "実行回数" #: xpack/plugins/cloud/serializers/task.py:37 msgid "Instance count" -msgstr "" +msgstr "インスタンス数" #: xpack/plugins/cloud/serializers/task.py:70 msgid "Linux admin user" -msgstr "" +msgstr "Linux管理者" #: xpack/plugins/cloud/serializers/task.py:75 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" -msgstr "" +msgstr "定期的な表示" #: xpack/plugins/cloud/utils.py:69 msgid "Account unavailable" -msgstr "" +msgstr "利用できないアカウント" #: xpack/plugins/gathered_user/meta.py:11 msgid "Gathered user" -msgstr "" +msgstr "収集されたユーザー" #: xpack/plugins/gathered_user/models.py:34 msgid "Gather user task" -msgstr "" +msgstr "ユーザータスクの収集" #: xpack/plugins/gathered_user/models.py:80 msgid "gather user task execution" -msgstr "" +msgstr "ユーザータスクの実行を収集" #: xpack/plugins/gathered_user/models.py:86 msgid "Assets is empty, please change nodes" -msgstr "" +msgstr "資産は空です。ノードを変更してください" #: xpack/plugins/gathered_user/serializers.py:21 msgid "Executed times" -msgstr "" +msgstr "実行時間" #: xpack/plugins/interface/api.py:52 msgid "Restore default successfully." -msgstr "" +msgstr "デフォルトの復元に成功しました。" #: xpack/plugins/interface/meta.py:10 msgid "Interface settings" -msgstr "" +msgstr "インターフェイスの設定" #: xpack/plugins/interface/models.py:22 msgid "Title of login page" -msgstr "" +msgstr "ログインページのタイトル" #: xpack/plugins/interface/models.py:26 msgid "Image of login page" -msgstr "" +msgstr "ログインページのイメージ" #: xpack/plugins/interface/models.py:30 msgid "Website icon" -msgstr "" +msgstr "ウェブサイトのアイコン" #: xpack/plugins/interface/models.py:34 msgid "Logo of management page" -msgstr "" +msgstr "管理ページのロゴ" #: xpack/plugins/interface/models.py:38 msgid "Logo of logout page" -msgstr "" +msgstr "ログアウトページのロゴ" #: xpack/plugins/interface/models.py:40 msgid "Theme" -msgstr "" +msgstr "テーマ" #: xpack/plugins/interface/models.py:43 xpack/plugins/interface/models.py:84 msgid "Interface setting" -msgstr "" +msgstr "インターフェイスの設定" #: xpack/plugins/license/api.py:50 msgid "License import successfully" -msgstr "" +msgstr "ライセンスのインポートに成功" #: xpack/plugins/license/api.py:51 msgid "License is invalid" -msgstr "" +msgstr "ライセンスが無効です" #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:127 msgid "License" -msgstr "" +msgstr "ライセンス" #: xpack/plugins/license/models.py:71 msgid "Standard edition" -msgstr "" +msgstr "標準版" #: xpack/plugins/license/models.py:73 msgid "Enterprise edition" -msgstr "" +msgstr "エンタープライズ版" #: xpack/plugins/license/models.py:75 msgid "Ultimate edition" -msgstr "" +msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" -msgstr "" +msgstr "コミュニティ版" + +#~ msgid "System User" +#~ msgstr "システムユーザー" + +#~ msgid "" +#~ "Format for comma-delimited string, with * indicating a match all. " +#~ "Protocol options: {}" +#~ msgstr "" +#~ "コンマ区切り文字列の形式。* はすべて一致することを示します。プロトコルオプ" +#~ "ション: {}" + +#~ msgid "Unsupported protocols: {}" +#~ msgstr "サポートされていないプロトコル: {}" + +#~ msgid "Remote app" +#~ msgstr "リモートアプリ" + +#~ msgid "Custom" +#~ msgstr "カスタム" + +#~ msgid "Can view application account secret" +#~ msgstr "アプリケーションアカウントの秘密を表示できます" + +#~ msgid "Can change application account secret" +#~ msgstr "アプリケーションアカウントの秘密を変更できます" + +#~ msgid "Application user" +#~ msgstr "アプリケーションユーザー" + +#~ msgid "Type display" +#~ msgstr "タイプ表示" + +#~ msgid "Application display" +#~ msgstr "アプリケーション表示" + +#~ msgid "Cluster" +#~ msgstr "クラスター" + +#~ msgid "CA certificate" +#~ msgstr "CA 証明書" + +#~ msgid "Client certificate file" +#~ msgstr "クライアント証明書" + +#~ msgid "Certificate key file" +#~ msgstr "証明書キー" + +#~ msgid "Asset Info" +#~ msgstr "資産情報" + +#~ msgid "Application path" +#~ msgstr "アプリケーションパス" + +#~ msgid "Target URL" +#~ msgstr "ターゲットURL" + +#~ msgid "Chrome username" +#~ msgstr "Chromeユーザー名" + +#~ msgid "Chrome password" +#~ msgstr "Chromeパスワード" + +#~ msgid "Operating parameter" +#~ msgstr "操作パラメータ" + +#~ msgid "Target url" +#~ msgstr "ターゲットURL" + +#~ msgid "Mysql workbench username" +#~ msgstr "Mysql workbench のユーザー名" + +#~ msgid "Mysql workbench password" +#~ msgstr "Mysql workbench パスワード" + +#~ msgid "Vmware username" +#~ msgstr "Vmware ユーザー名" + +#~ msgid "Vmware password" +#~ msgstr "Vmware パスワード" + +#~ msgid "Base" +#~ msgstr "ベース" + +#~ msgid "Can test asset account connectivity" +#~ msgstr "アセットアカウントの接続性をテストできます" + +#~ msgid "Bandwidth" +#~ msgstr "帯域幅" + +#~ msgid "Contact" +#~ msgstr "連絡先" + +#~ msgid "Intranet" +#~ msgstr "イントラネット" + +#~ msgid "Extranet" +#~ msgstr "エクストラネット" + +#~ msgid "Operator" +#~ msgstr "オペレーター" + +#~ msgid "Default Cluster" +#~ msgstr "デフォルトクラスター" + +#~ msgid "Test gateway" +#~ msgstr "テストゲートウェイ" + +#~ msgid "User groups" +#~ msgstr "ユーザーグループ" + +#~ msgid "System user display" +#~ msgstr "システムユーザー表示" + +#~ msgid "Protocol format should {}/{}" +#~ msgstr "プロトコル形式は {}/{}" + +#~ msgid "Nodes name" +#~ msgstr "ノード名" + +#~ msgid "Labels name" +#~ msgstr "ラベル名" + +#~ msgid "Hardware info" +#~ msgstr "ハードウェア情報" + +#~ msgid "Admin user display" +#~ msgstr "管理者ユーザー表示" + +#~ msgid "CPU info" +#~ msgstr "CPU情報" + +#~ msgid "Action display" +#~ msgstr "アクション表示" + +#~ msgid "Applications amount" +#~ msgstr "申し込み金額" + +#~ msgid "Gateways count" +#~ msgstr "ゲートウェイ数" + +#~ msgid "SSH key fingerprint" +#~ msgstr "SSHキー指紋" + +#~ msgid "Apps amount" +#~ msgstr "アプリの量" + +#~ msgid "Nodes amount" +#~ msgstr "ノード量" + +#~ msgid "Login mode display" +#~ msgstr "ログインモード表示" + +#~ msgid "Ad domain" +#~ msgstr "広告ドメイン" + +#~ msgid "Is asset protocol" +#~ msgstr "資産プロトコルです" + +#~ msgid "Only ssh and automatic login system users are supported" +#~ msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" + +#~ msgid "Username same with user with protocol {} only allow 1" +#~ msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" + +#~ msgid "* Automatic login mode must fill in the username." +#~ msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" + +#~ msgid "Path should starts with /" +#~ msgstr "パスは/で始まる必要があります" + +#~ msgid "Password or private key required" +#~ msgstr "パスワードまたは秘密鍵が必要" + +#~ msgid "Only ssh protocol system users are allowed" +#~ msgstr "Sshプロトコルシステムユーザーのみが許可されています" + +#~ msgid "The protocol must be consistent with the current user: {}" +#~ msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" + +#~ msgid "Only system users with automatic login are allowed" +#~ msgstr "自動ログインを持つシステムユーザーのみが許可されます" + +#~ msgid "System user name" +#~ msgstr "システムユーザー名" + +#~ msgid "Asset hostname" +#~ msgstr "資産ホスト名" + +#~ msgid "The asset {} system platform {} does not support run Ansible tasks" +#~ msgstr "" +#~ "資産 {} システムプラットフォーム {} はAnsibleタスクの実行をサポートしてい" +#~ "ません。" + +#~ msgid "Test assets connectivity: " +#~ msgstr "資産の接続性のテスト:" + +#~ msgid "Unreachable" +#~ msgstr "達成できない" + +#~ msgid "Reachable" +#~ msgstr "接続可能" + +#~ msgid "Get asset info failed: {}" +#~ msgstr "資産情報の取得に失敗しました: {}" + +#~ msgid "Update asset hardware info: " +#~ msgstr "資産ハードウェア情報の更新:" + +#~ msgid "System user is dynamic: {}" +#~ msgstr "システムユーザーは動的です: {}" + +#~ msgid "Start push system user for platform: [{}]" +#~ msgstr "プラットフォームのプッシュシステムユーザーを開始: [{}]" + +#~ msgid "Hosts count: {}" +#~ msgstr "ホスト数: {}" + +#~ msgid "Push system users to asset: " +#~ msgstr "システムユーザーをアセットにプッシュする:" + +#~ msgid "Dynamic system user not support test" +#~ msgstr "動的システムユーザーがテストをサポートしていない" + +#~ msgid "Start test system user connectivity for platform: [{}]" +#~ msgstr "プラットフォームのテストシステムのユーザー接続を開始: [{}]" + +#~ msgid "Test system user connectivity: " +#~ msgstr "テストシステムユーザー接続:" + +#~ msgid "Test system user connectivity period: " +#~ msgstr "テストシステムユーザー接続期间:" + +#~ msgid "Operate display" +#~ msgstr "ディスプレイを操作する" + +#~ msgid "Status display" +#~ msgstr "ステータス表示" + +#~ msgid "MFA display" +#~ msgstr "MFAディスプレイ" + +#~ msgid "Hosts display" +#~ msgstr "ホスト表示" + +#~ msgid "Run as" +#~ msgstr "として実行" + +#~ msgid "Run as display" +#~ msgstr "ディスプレイとして実行する" + +#~ msgid "User not exists" +#~ msgstr "ユーザーは存在しません" + +#~ msgid "System user not exists" +#~ msgstr "システムユーザーが存在しません" + +#~ msgid "Asset not exists" +#~ msgstr "アセットが存在しません" + +#~ msgid "User has no permission to access asset or permission expired" +#~ msgstr "" +#~ "ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れ" +#~ "ています" + +#~ msgid "User has no permission to access application or permission expired" +#~ msgstr "" +#~ "ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れて" +#~ "います" + +#~ msgid "Asset or application required" +#~ msgstr "アセットまたはアプリが必要" + +#~ msgid "Not has host {} permission" +#~ msgstr "ホスト {} 権限がありません" + +#~ msgid "" +#~ "eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " +#~ "crontab expressions (Online tools)
Note: If both Regularly " +#~ "perform and Cycle perform are set, give priority to Regularly perform" +#~ msgstr "" +#~ "eg:毎週日03:05<5 3**0>
ヒント:5ビットLinux crontab式<分時日月曜日>(オンラインワーク)
" +#~ "注意:定期実行と周期実行を同時に設定した場合は、定期実行を優先します。" + +#~ msgid "Unit: hour" +#~ msgstr "単位: 時間" + +#~ msgid "Callback" +#~ msgstr "コールバック" + +#~ msgid "Can view task monitor" +#~ msgstr "タスクモニターを表示できます" + +#~ msgid "Tasks" +#~ msgstr "タスク" + +#~ msgid "Options" +#~ msgstr "オプション" + +#~ msgid "Run as admin" +#~ msgstr "再実行" + +#~ msgid "Become" +#~ msgstr "になる" + +#~ msgid "Create by" +#~ msgstr "による作成" + +#~ msgid "AdHoc" +#~ msgstr "タスクの各バージョン" + +#~ msgid "Task display" +#~ msgstr "タスク表示" + +#~ msgid "Host amount" +#~ msgstr "ホスト量" + +#~ msgid "Start time" +#~ msgstr "開始時間" + +#~ msgid "End time" +#~ msgstr "終了時間" + +#~ msgid "Adhoc raw result" +#~ msgstr "アドホック生の結果" + +#~ msgid "Adhoc result summary" +#~ msgstr "アドホック結果の概要" + +#~ msgid "AdHoc execution" +#~ msgstr "アドホックエキューション" + +#~ msgid "Task start" +#~ msgstr "タスクの開始" + +#~ msgid "Command `{}` is forbidden ........" +#~ msgstr "コマンド '{}' は禁止されています ........" + +#~ msgid "Task end" +#~ msgstr "タスク" + +#~ msgid "Clean task history period" +#~ msgstr "クリーンなタスク履歴期間" + +#~ msgid "The administrator is modifying permissions. Please wait" +#~ msgstr "管理者は権限を変更しています。お待ちください" + +#~ msgid "The authorization cannot be revoked for the time being" +#~ msgstr "当分の間、承認を取り消すことはできません。" + +#~ msgid "Application permission" +#~ msgstr "申請許可" + +#~ msgid "Permed application" +#~ msgstr "許可されたアプリケーション" + +#~ msgid "Can view my apps" +#~ msgstr "自分のアプリを表示できます" + +#~ msgid "Can view user apps" +#~ msgstr "ユーザーアプリを表示できます" + +#~ msgid "Can view usergroup apps" +#~ msgstr "ユーザー・グループ認可の適用を表示できます" + +#~ msgid "Upload file" +#~ msgstr "ファイルのアップロード" + +#~ msgid "Download file" +#~ msgstr "ファイルのダウンロード" + +#~ msgid "Upload download" +#~ msgstr "ダウンロードのアップロード" + +#~ msgid "Clipboard paste" +#~ msgstr "クリップボードペースト" + +#~ msgid "Clipboard copy paste" +#~ msgstr "クリップボードコピーペースト" + +#~ msgid "Your permed applications is about to expire" +#~ msgstr "パーマアプリケーションの有効期限が近づいています" + +#~ msgid "permed applications" +#~ msgstr "Permedアプリケーション" + +#~ msgid "Application permissions is about to expire" +#~ msgstr "アプリケーション権限の有効期限が近づいています" + +#~ msgid "application permissions of organization {}" +#~ msgstr "Organization {} のアプリケーション権限" + +#~ msgid "User groups amount" +#~ msgstr "ユーザーグループの量" + +#~ msgid "System users amount" +#~ msgstr "システムユーザー数" + +#~ msgid "" +#~ "The application list contains applications that are different from the " +#~ "permission type. ({})" +#~ msgstr "" +#~ "アプリケーションリストには、権限タイプとは異なるアプリケーションが含まれて" +#~ "います。({})" + +#~ msgid "Users display" +#~ msgstr "ユーザー表示" + +#~ msgid "User groups display" +#~ msgstr "ユーザーグループの表示" + +#~ msgid "Assets display" +#~ msgstr "資産表示" + +#~ msgid "Nodes display" +#~ msgstr "ノード表示" + +#~ msgid "System users display" +#~ msgstr "システムユーザーの表示" + +#~ msgid "My applications" +#~ msgstr "私のアプリケーション" + +#~ msgid "Empty" +#~ msgstr "空" + +#~ msgid "System user ID" +#~ msgstr "システムユーザーID" + +#~ msgid "Apply for application" +#~ msgstr "申し込み" + +#~ msgid "" +#~ "Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " +#~ "processor: {}, ticket ID: {}" +#~ msgstr "" +#~ "チケットによって作成されたチケットタイトル: {}、チケット申請者: {}、チケッ" +#~ "ト処理者: {}、チケットID: {}" + +#~ msgid "Applied login IP" +#~ msgstr "応用ログインIP" + +#~ msgid "Applied login city" +#~ msgstr "応用ログイン都市" + +#~ msgid "Applied login datetime" +#~ msgstr "適用されたログインの日付時間" + +#~ msgid "Login system user" +#~ msgstr "ログインシステムユーザー" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 19eccde31..0fc454c7f 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:6c0ba1103efe746ecf579fe27832b5d2969858508f4aabdcc42723b13c1b01f8 -size 383 +oid sha256:6bd3c45b4301a45fa1b110716b3ea78bcbb53a2913f707ab754882df061256cc +size 98368 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 63a82105c..c57c03c00 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -3,36 +3,38 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-12-06 17:33+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"PO-Revision-Date: 2021-05-20 10:54+0800\n" +"Last-Translator: ibuler \n" +"Language-Team: JumpServer team\n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.3\n" #: acls/apps.py:7 msgid "Acls" -msgstr "" +msgstr "访问控制" #: acls/models/base.py:20 tickets/const.py:45 #: tickets/templates/tickets/approve_check_password.html:49 msgid "Reject" -msgstr "" +msgstr "拒绝" #: acls/models/base.py:21 +#, fuzzy msgid "Accept" -msgstr "" +msgstr "被接受" #: acls/models/base.py:22 +#, fuzzy msgid "Review" -msgstr "" +msgstr "审批人" #: acls/models/base.py:71 acls/models/command_acl.py:22 #: acls/serializers/base.py:34 applications/models.py:10 @@ -53,36 +55,36 @@ msgstr "" #: users/models/group.py:15 users/models/user.py:675 #: xpack/plugins/cloud/models.py:30 msgid "Name" -msgstr "" +msgstr "名称" #: acls/models/base.py:73 assets/models/_user.py:47 #: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:89 msgid "Priority" -msgstr "" +msgstr "优先级" #: acls/models/base.py:74 assets/models/_user.py:47 #: assets/models/cmd_filter.py:81 terminal/models/component/endpoint.py:90 msgid "1-100, the lower the value will be match first" -msgstr "" +msgstr "优先级可选范围为 1-100 (数值越小越优先)" #: acls/models/base.py:77 acls/serializers/base.py:63 #: assets/models/cmd_filter.py:86 audits/models.py:51 audits/serializers.py:75 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" -msgstr "" +msgstr "动作" #: acls/models/base.py:78 acls/serializers/base.py:59 #: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:91 #: authentication/serializers/connect_token_secret.py:79 msgid "Reviewers" -msgstr "" +msgstr "审批人" #: acls/models/base.py:79 authentication/models/access_key.py:17 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 #: tickets/const.py:37 msgid "Active" -msgstr "" +msgstr "激活中" #: acls/models/base.py:80 acls/models/command_acl.py:29 #: applications/models.py:19 assets/models/_user.py:40 @@ -104,7 +106,7 @@ msgstr "" #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:121 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" -msgstr "" +msgstr "备注" #: acls/models/base.py:92 acls/models/login_acl.py:13 #: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 @@ -122,7 +124,7 @@ msgstr "" #: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:907 #: users/models/user.py:938 users/serializers/group.py:19 msgid "User" -msgstr "" +msgstr "用户" #: acls/models/base.py:94 acls/serializers/base.py:56 #: assets/models/account.py:51 assets/models/asset/common.py:83 @@ -141,7 +143,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:172 #: xpack/plugins/cloud/models.py:222 msgid "Asset" -msgstr "" +msgstr "资产" #: acls/models/base.py:96 acls/serializers/base.py:57 #: assets/models/account.py:61 @@ -151,7 +153,7 @@ msgstr "" #: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:71 msgid "Account" -msgstr "" +msgstr "账号" #: acls/models/command_acl.py:17 assets/models/cmd_filter.py:65 #: terminal/backends/command/serializers.py:15 @@ -159,11 +161,11 @@ msgstr "" #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" -msgstr "" +msgstr "命令" #: acls/models/command_acl.py:18 assets/models/cmd_filter.py:64 msgid "Regex" -msgstr "" +msgstr "正则表达式" #: acls/models/command_acl.py:25 acls/serializers/command_acl.py:14 #: applications/models.py:15 assets/models/_user.py:46 @@ -181,65 +183,68 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" -msgstr "" +msgstr "类型" #: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 #: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 msgid "Content" -msgstr "" +msgstr "内容" #: acls/models/command_acl.py:27 assets/models/cmd_filter.py:84 msgid "One line one command" -msgstr "" +msgstr "每行一个命令" #: acls/models/command_acl.py:28 assets/models/cmd_filter.py:85 msgid "Ignore case" -msgstr "" +msgstr "忽略大小写" #: acls/models/command_acl.py:35 acls/serializers/command_acl.py:24 #: authentication/serializers/connect_token_secret.py:76 +#, fuzzy msgid "Command group" -msgstr "" +msgstr "命令记录" #: acls/models/command_acl.py:88 msgid "The generated regular expression is incorrect: {}" -msgstr "" +msgstr "生成的正则表达式有误" #: acls/models/command_acl.py:98 +#, fuzzy msgid "Commands" -msgstr "" +msgstr "命令" #: acls/models/command_acl.py:102 +#, fuzzy msgid "Command acl" -msgstr "" +msgstr "命令" #: acls/models/command_acl.py:111 tickets/const.py:11 msgid "Command confirm" -msgstr "" +msgstr "命令复核" #: acls/models/login_acl.py:16 msgid "Rule" -msgstr "" +msgstr "规则" #: acls/models/login_acl.py:19 msgid "Login acl" -msgstr "" +msgstr "登录访问控制" #: acls/models/login_acl.py:54 tickets/const.py:10 msgid "Login confirm" -msgstr "" +msgstr "登录复核" #: acls/models/login_asset_acl.py:10 msgid "Login asset acl" -msgstr "" +msgstr "登录资产访问控制" #: acls/models/login_asset_acl.py:20 tickets/const.py:12 msgid "Login asset confirm" -msgstr "" +msgstr "登录资产复核" #: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 msgid "Format for comma-delimited string, with * indicating a match all. " -msgstr "" +msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/base.py:18 acls/serializers/base.py:49 #: assets/models/_user.py:34 assets/models/base.py:65 @@ -254,7 +259,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/asset.py:196 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Username" -msgstr "" +msgstr "用户名" #: acls/serializers/base.py:25 msgid "" @@ -262,23 +267,26 @@ msgid "" "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" "db8:1a:1110::/64 (Domain name support)" msgstr "" +"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " +"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" #: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 +#, fuzzy msgid "IP/Host" -msgstr "" +msgstr "主机" #: acls/serializers/base.py:90 tickets/serializers/ticket/ticket.py:79 msgid "The organization `{}` does not exist" -msgstr "" +msgstr "组织 `{}` 不存在" #: acls/serializers/base.py:96 msgid "None of the reviewers belong to Organization `{}`" -msgstr "" +msgstr "所有复核人都不属于组织 `{}`" #: acls/serializers/rules/rules.py:20 #: xpack/plugins/cloud/serializers/task.py:23 msgid "IP address invalid: `{}`" -msgstr "" +msgstr "IP 地址无效: `{}`" #: acls/serializers/rules/rules.py:25 msgid "" @@ -286,6 +294,8 @@ msgid "" "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" "db8:1a:1110::/64 " msgstr "" +"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " +"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" #: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 #: authentication/templates/authentication/_msg_oauth_bind.html:12 @@ -293,15 +303,15 @@ msgstr "" #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 #: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54 msgid "IP" -msgstr "" +msgstr "IP" #: acls/serializers/rules/rules.py:35 msgid "Time Period" -msgstr "" +msgstr "时段" #: applications/apps.py:9 msgid "Applications" -msgstr "" +msgstr "应用管理" #: applications/models.py:12 assets/models/label.py:20 #: assets/models/platform.py:73 assets/serializers/asset/common.py:62 @@ -310,72 +320,73 @@ msgstr "" #: settings/models.py:35 tickets/models/ticket/apply_application.py:13 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" -msgstr "" +msgstr "类别" #: applications/models.py:17 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/serializers/account.py:64 msgid "Attrs" -msgstr "" +msgstr "属性" #: applications/models.py:23 xpack/plugins/change_auth_plan/models/app.py:31 msgid "Application" -msgstr "" +msgstr "应用程序" #: applications/models.py:27 msgid "Can match application" -msgstr "" +msgstr "匹配应用" #: applications/serializers/attrs/application_type/clickhouse.py:11 #: assets/models/asset/common.py:82 assets/models/platform.py:22 #: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" -msgstr "" +msgstr "端口" #: applications/serializers/attrs/application_type/clickhouse.py:13 msgid "" "Typically, the port is 9000,the HTTP interface and the native interface use " "different ports" -msgstr "" +msgstr "默认端口为9000, HTTP接口和本机接口使用不同的端口" #: assets/api/automations/base.py:76 #: xpack/plugins/change_auth_plan/api/asset.py:94 msgid "The parameter 'action' must be [{}]" -msgstr "" +msgstr "参数 'action' 必须是 [{}]" #: assets/api/domain.py:56 msgid "Number required" -msgstr "" +msgstr "需要为数字" #: assets/api/node.py:62 msgid "You can't update the root node name" -msgstr "" +msgstr "不能修改根节点名称" #: assets/api/node.py:69 msgid "You can't delete the root node ({})" -msgstr "" +msgstr "不能删除根节点 ({})" #: assets/api/node.py:72 msgid "Deletion failed and the node contains assets" -msgstr "" +msgstr "删除失败,节点包含资产" #: assets/apps.py:9 msgid "App assets" -msgstr "" +msgstr "资产管理" #: assets/automations/base/manager.py:123 +#, fuzzy msgid "{} disabled" -msgstr "" +msgstr "禁用" #: assets/const/account.py:6 audits/const.py:6 audits/const.py:64 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/utils.py:84 msgid "Unknown" -msgstr "" +msgstr "未知" #: assets/const/account.py:7 msgid "Ok" -msgstr "" +msgstr "成功" #: assets/const/account.py:8 #: assets/serializers/automations/change_secret.py:118 @@ -384,7 +395,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:41 msgid "Failed" -msgstr "" +msgstr "失败" #: assets/const/account.py:12 assets/models/_user.py:35 #: audits/signal_handlers.py:49 authentication/confirm/password.py:9 @@ -401,44 +412,50 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/base.py:73 #: xpack/plugins/cloud/serializers/account_attrs.py:28 msgid "Password" -msgstr "" +msgstr "密码" #: assets/const/account.py:13 +#, fuzzy msgid "SSH key" -msgstr "" +msgstr "SSH 密钥" #: assets/const/account.py:14 authentication/models/access_key.py:33 msgid "Access key" -msgstr "" +msgstr "Access key" #: assets/const/account.py:15 assets/models/_user.py:38 #: authentication/models/sso_token.py:14 msgid "Token" -msgstr "" +msgstr "Token" #: assets/const/automation.py:13 msgid "Ping" msgstr "" #: assets/const/automation.py:14 +#, fuzzy msgid "Gather facts" -msgstr "" +msgstr "收集账号" #: assets/const/automation.py:15 +#, fuzzy msgid "Create account" -msgstr "" +msgstr "收集账号" #: assets/const/automation.py:16 +#, fuzzy msgid "Change secret" -msgstr "" +msgstr "执行改密" #: assets/const/automation.py:17 +#, fuzzy msgid "Verify account" -msgstr "" +msgstr "验证密码/密钥" #: assets/const/automation.py:18 +#, fuzzy msgid "Gather accounts" -msgstr "" +msgstr "收集账号" #: assets/const/automation.py:38 assets/serializers/account/base.py:26 msgid "Specific" @@ -447,34 +464,34 @@ msgstr "" #: assets/const/automation.py:39 ops/const.py:20 #: xpack/plugins/change_auth_plan/models/base.py:28 msgid "All assets use the same random password" -msgstr "" +msgstr "使用相同的随机密码" #: assets/const/automation.py:40 ops/const.py:21 #: xpack/plugins/change_auth_plan/models/base.py:29 msgid "All assets use different random password" -msgstr "" +msgstr "使用不同的随机密码" #: assets/const/automation.py:44 ops/const.py:13 #: xpack/plugins/change_auth_plan/models/asset.py:30 msgid "Append SSH KEY" -msgstr "" +msgstr "追加" #: assets/const/automation.py:45 ops/const.py:14 #: xpack/plugins/change_auth_plan/models/asset.py:31 msgid "Empty and append SSH KEY" -msgstr "" +msgstr "清空所有并添加" #: assets/const/automation.py:46 ops/const.py:15 #: xpack/plugins/change_auth_plan/models/asset.py:32 msgid "Replace (The key generated by JumpServer) " -msgstr "" +msgstr "替换 (由 JumpServer 生成的密钥)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:16 #: settings/serializers/auth/sms.py:67 terminal/models/applet/applet.py:59 #: terminal/models/component/endpoint.py:13 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" -msgstr "" +msgstr "主机" #: assets/const/category.py:12 msgid "Device" @@ -483,11 +500,12 @@ msgstr "" #: assets/const/category.py:13 assets/models/asset/database.py:8 #: assets/models/asset/database.py:34 msgid "Database" -msgstr "" +msgstr "数据库" #: assets/const/category.py:14 +#, fuzzy msgid "Cloud service" -msgstr "" +msgstr "云管中心" #: assets/const/category.py:15 audits/const.py:62 #: terminal/models/applet/applet.py:18 @@ -497,11 +515,12 @@ msgstr "" #: assets/const/device.py:7 terminal/models/applet/applet.py:17 #: tickets/const.py:8 msgid "General" -msgstr "" +msgstr "一般" #: assets/const/device.py:8 +#, fuzzy msgid "Switch" -msgstr "" +msgstr "切换自" #: assets/const/device.py:9 msgid "Router" @@ -512,36 +531,37 @@ msgid "Firewall" msgstr "" #: assets/const/web.py:7 +#, fuzzy msgid "Website" -msgstr "" +msgstr "网站图标" #: assets/models/_user.py:24 msgid "Automatic managed" -msgstr "" +msgstr "托管密码" #: assets/models/_user.py:25 msgid "Manually input" -msgstr "" +msgstr "手动输入" #: assets/models/_user.py:29 msgid "Common user" -msgstr "" +msgstr "普通用户" #: assets/models/_user.py:30 msgid "Admin user" -msgstr "" +msgstr "特权用户" #: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" -msgstr "" +msgstr "SSH密钥" #: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 #: xpack/plugins/change_auth_plan/models/asset.py:127 #: xpack/plugins/change_auth_plan/models/asset.py:203 msgid "SSH public key" -msgstr "" +msgstr "SSH公钥" #: assets/models/_user.py:41 assets/models/automations/base.py:92 #: assets/models/cmd_filter.py:46 assets/models/domain.py:23 @@ -551,13 +571,13 @@ msgstr "" #: users/models/group.py:18 users/models/user.py:939 #: xpack/plugins/change_auth_plan/models/base.py:45 msgid "Date created" -msgstr "" +msgstr "创建日期" #: assets/models/_user.py:42 assets/models/cmd_filter.py:47 #: assets/models/gathered_user.py:20 common/db/models.py:78 #: common/mixins/models.py:51 xpack/plugins/change_auth_plan/models/base.py:46 msgid "Date updated" -msgstr "" +msgstr "更新日期" #: assets/models/_user.py:43 assets/models/base.py:73 #: assets/models/cmd_filter.py:49 assets/models/cmd_filter.py:96 @@ -566,128 +586,134 @@ msgstr "" #: users/models/user.py:722 users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 msgid "Created by" -msgstr "" +msgstr "创建者" #: assets/models/_user.py:45 msgid "Username same with user" -msgstr "" +msgstr "用户名与用户相同" #: assets/models/_user.py:48 authentication/models/connection_token.py:34 #: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 #: terminal/serializers/session.py:18 terminal/serializers/session.py:32 #: terminal/serializers/storage.py:68 msgid "Protocol" -msgstr "" +msgstr "协议" #: assets/models/_user.py:49 msgid "Auto push" -msgstr "" +msgstr "自动推送" #: assets/models/_user.py:50 msgid "Sudo" -msgstr "" +msgstr "Sudo" #: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 msgid "Shell" -msgstr "" +msgstr "Shell" #: assets/models/_user.py:52 msgid "Login mode" -msgstr "" +msgstr "认证方式" #: assets/models/_user.py:53 msgid "SFTP Root" -msgstr "" +msgstr "SFTP根路径" #: assets/models/_user.py:54 msgid "Home" -msgstr "" +msgstr "家目录" #: assets/models/_user.py:55 msgid "System groups" -msgstr "" +msgstr "用户组" #: assets/models/_user.py:58 msgid "User switch" -msgstr "" +msgstr "用户切换" #: assets/models/_user.py:59 msgid "Switch from" -msgstr "" +msgstr "切换自" #: assets/models/_user.py:65 audits/models.py:35 #: xpack/plugins/change_auth_plan/models/app.py:35 #: xpack/plugins/change_auth_plan/models/app.py:146 msgid "System user" -msgstr "" +msgstr "系统用户" #: assets/models/_user.py:67 msgid "Can match system user" -msgstr "" +msgstr "可以匹配系统用户" #: assets/models/account.py:45 common/db/fields.py:232 #: settings/serializers/terminal.py:14 msgid "All" -msgstr "" +msgstr "全部" #: assets/models/account.py:46 +#, fuzzy msgid "Manual input" -msgstr "" +msgstr "手动输入" #: assets/models/account.py:47 +#, fuzzy msgid "Dynamic user" -msgstr "" +msgstr "动态码" #: assets/models/account.py:55 #: authentication/serializers/connect_token_secret.py:47 +#, fuzzy msgid "Su from" -msgstr "" +msgstr "切换自" #: assets/models/account.py:57 settings/serializers/auth/cas.py:20 #: terminal/models/applet/applet.py:22 msgid "Version" -msgstr "" +msgstr "版本" #: assets/models/account.py:67 msgid "Can view asset account secret" -msgstr "" +msgstr "可以查看资产账号密码" #: assets/models/account.py:68 msgid "Can change asset account secret" -msgstr "" +msgstr "可以更改资产账号密码" #: assets/models/account.py:69 msgid "Can view asset history account" -msgstr "" +msgstr "可以查看资产历史账号" #: assets/models/account.py:70 msgid "Can view asset history account secret" -msgstr "" +msgstr "可以查看资产历史账号密码" #: assets/models/account.py:93 assets/serializers/account/account.py:15 +#, fuzzy msgid "Account template" -msgstr "" +msgstr "账号名称" #: assets/models/account.py:98 +#, fuzzy msgid "Can view asset account template secret" -msgstr "" +msgstr "可以查看资产账号密码" #: assets/models/account.py:99 +#, fuzzy msgid "Can change asset account template secret" -msgstr "" +msgstr "可以更改资产账号密码" #: assets/models/asset/common.py:93 assets/models/platform.py:110 #: assets/serializers/asset/common.py:65 #: perms/serializers/user_permission.py:21 #: xpack/plugins/cloud/serializers/account_attrs.py:179 msgid "Platform" -msgstr "" +msgstr "系统平台" #: assets/models/asset/common.py:95 assets/models/domain.py:26 #: assets/serializers/asset/common.py:64 #: authentication/serializers/connect_token_secret.py:105 msgid "Domain" -msgstr "" +msgstr "网域" #: assets/models/asset/common.py:97 assets/models/automations/base.py:18 #: assets/models/cmd_filter.py:37 assets/serializers/asset/common.py:66 @@ -696,88 +722,95 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" -msgstr "" +msgstr "节点" #: assets/models/asset/common.py:98 assets/models/automations/base.py:21 #: assets/models/base.py:71 assets/models/cmd_filter.py:44 #: assets/models/label.py:21 terminal/models/applet/applet.py:25 #: users/serializers/user.py:202 msgid "Is active" -msgstr "" +msgstr "激活" #: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 msgid "Labels" -msgstr "" +msgstr "标签管理" #: assets/models/asset/common.py:215 msgid "Can refresh asset hardware info" -msgstr "" +msgstr "可以更新资产硬件信息" #: assets/models/asset/common.py:216 msgid "Can test asset connectivity" -msgstr "" +msgstr "可以测试资产连接性" #: assets/models/asset/common.py:217 +#, fuzzy msgid "Can push account to asset" -msgstr "" +msgstr "可以推送系统用户到资产" #: assets/models/asset/common.py:218 msgid "Can match asset" -msgstr "" +msgstr "可以匹配资产" #: assets/models/asset/common.py:219 msgid "Add asset to node" -msgstr "" +msgstr "添加资产到节点" #: assets/models/asset/common.py:220 msgid "Move asset to node" -msgstr "" +msgstr "移动资产到节点" #: assets/models/asset/database.py:9 settings/serializers/email.py:37 msgid "Use SSL" -msgstr "" +msgstr "使用 SSL" #: assets/models/asset/database.py:10 +#, fuzzy msgid "CA cert" -msgstr "" +msgstr "SP 证书" #: assets/models/asset/database.py:11 +#, fuzzy msgid "Client cert" -msgstr "" +msgstr "客户端密钥" #: assets/models/asset/database.py:12 +#, fuzzy msgid "Client key" -msgstr "" +msgstr "客户端" #: assets/models/asset/database.py:13 msgid "Allow invalid cert" -msgstr "" +msgstr "忽略证书校验" #: assets/models/asset/web.py:9 audits/const.py:68 #: terminal/serializers/applet_host.py:25 msgid "Disabled" -msgstr "" +msgstr "禁用" #: assets/models/asset/web.py:10 settings/serializers/auth/base.py:10 #: settings/serializers/basic.py:27 msgid "Basic" -msgstr "" +msgstr "基本" #: assets/models/asset/web.py:11 assets/models/asset/web.py:17 msgid "Script" msgstr "" #: assets/models/asset/web.py:13 +#, fuzzy msgid "Autofill" -msgstr "" +msgstr "自动" #: assets/models/asset/web.py:14 assets/serializers/platform.py:30 +#, fuzzy msgid "Username selector" -msgstr "" +msgstr "用户名属性" #: assets/models/asset/web.py:15 assets/serializers/platform.py:33 +#, fuzzy msgid "Password selector" -msgstr "" +msgstr "密码规则" #: assets/models/asset/web.py:16 assets/serializers/platform.py:36 msgid "Submit selector" @@ -787,7 +820,7 @@ msgstr "" #: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 #: perms/serializers/permission.py:32 rbac/tree.py:37 msgid "Accounts" -msgstr "" +msgstr "账号管理" #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 ops/models/base.py:17 @@ -795,11 +828,12 @@ msgstr "" #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" -msgstr "" +msgstr "资产" #: assets/models/automations/base.py:82 assets/models/automations/base.py:89 +#, fuzzy msgid "Automation task" -msgstr "" +msgstr "托管密码" #: assets/models/automations/base.py:91 audits/models.py:129 #: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 @@ -808,7 +842,7 @@ msgstr "" #: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:20 #: xpack/plugins/cloud/models.py:174 xpack/plugins/cloud/models.py:226 msgid "Status" -msgstr "" +msgstr "状态" #: assets/models/automations/base.py:93 assets/models/backup.py:76 #: audits/models.py:41 ops/models/base.py:55 ops/models/celery.py:59 @@ -820,19 +854,20 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:199 #: xpack/plugins/gathered_user/models.py:71 msgid "Date start" -msgstr "" +msgstr "开始日期" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 #: ops/models/celery.py:60 ops/models/job.py:110 #: terminal/models/applet/host.py:106 msgid "Date finished" -msgstr "" +msgstr "结束日期" #: assets/models/automations/base.py:96 #: assets/serializers/automations/base.py:39 +#, fuzzy msgid "Automation snapshot" -msgstr "" +msgstr "工单快照" #: assets/models/automations/base.py:100 assets/models/backup.py:87 #: assets/serializers/account/backup.py:37 @@ -840,22 +875,25 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" -msgstr "" +msgstr "触发模式" #: assets/models/automations/base.py:104 #: assets/serializers/automations/change_secret.py:103 +#, fuzzy msgid "Automation task execution" -msgstr "" +msgstr "同步实例任务执行" #: assets/models/automations/change_secret.py:15 assets/models/base.py:67 #: assets/serializers/account/account.py:97 assets/serializers/base.py:13 +#, fuzzy msgid "Secret type" -msgstr "" +msgstr "Secret key" #: assets/models/automations/change_secret.py:19 #: assets/serializers/automations/change_secret.py:25 +#, fuzzy msgid "Secret strategy" -msgstr "" +msgstr "Secret key" #: assets/models/automations/change_secret.py:21 #: assets/models/automations/change_secret.py:57 assets/models/base.py:69 @@ -863,16 +901,17 @@ msgstr "" #: authentication/templates/authentication/_access_key_modal.html:31 #: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:19 msgid "Secret" -msgstr "" +msgstr "密钥" #: assets/models/automations/change_secret.py:22 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" -msgstr "" +msgstr "密码规则" #: assets/models/automations/change_secret.py:25 +#, fuzzy msgid "SSH key change strategy" -msgstr "" +msgstr "SSH 密钥策略" #: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 #: assets/serializers/account/backup.py:30 @@ -881,27 +920,32 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/asset.py:63 #: xpack/plugins/change_auth_plan/serializers/base.py:45 msgid "Recipient" -msgstr "" +msgstr "收件人" #: assets/models/automations/change_secret.py:34 +#, fuzzy msgid "Change secret automation" -msgstr "" +msgstr "安全设置" #: assets/models/automations/change_secret.py:56 +#, fuzzy msgid "Old secret" -msgstr "" +msgstr "OTP 秘钥" #: assets/models/automations/change_secret.py:58 +#, fuzzy msgid "Date started" -msgstr "" +msgstr "开始日期" #: assets/models/automations/change_secret.py:61 common/const/choices.py:20 +#, fuzzy msgid "Error" -msgstr "" +msgstr "企业微信错误" #: assets/models/automations/change_secret.py:64 +#, fuzzy msgid "Change secret record" -msgstr "" +msgstr "更改密码" #: assets/models/automations/discovery_account.py:8 msgid "Discovery account automation" @@ -909,28 +953,33 @@ msgstr "" #: assets/models/automations/gather_accounts.py:15 #: assets/tasks/gather_accounts.py:28 +#, fuzzy msgid "Gather asset accounts" -msgstr "" +msgstr "收集账号" #: assets/models/automations/gather_facts.py:15 +#, fuzzy msgid "Gather asset facts" -msgstr "" +msgstr "收集资产上的用户" #: assets/models/automations/ping.py:15 +#, fuzzy msgid "Ping asset" -msgstr "" +msgstr "登录资产" #: assets/models/automations/push_account.py:16 +#, fuzzy msgid "Push asset account" -msgstr "" +msgstr "服务账号" #: assets/models/automations/verify_account.py:15 +#, fuzzy msgid "Verify asset account" -msgstr "" +msgstr "验证密码/密钥" #: assets/models/backup.py:37 assets/models/backup.py:95 msgid "Account backup plan" -msgstr "" +msgstr "账号备份计划" #: assets/models/backup.py:79 #: authentication/templates/authentication/_msg_oauth_bind.html:11 @@ -939,11 +988,11 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:200 #: xpack/plugins/gathered_user/models.py:74 msgid "Time" -msgstr "" +msgstr "时间" #: assets/models/backup.py:83 msgid "Account backup snapshot" -msgstr "" +msgstr "账号备份快照" #: assets/models/backup.py:90 audits/models.py:124 #: terminal/models/session/sharing.py:108 @@ -951,7 +1000,7 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:171 #: xpack/plugins/cloud/models.py:178 msgid "Reason" -msgstr "" +msgstr "原因" #: assets/models/backup.py:92 #: assets/serializers/automations/change_secret.py:99 @@ -960,19 +1009,19 @@ msgstr "" #: xpack/plugins/change_auth_plan/models/base.py:198 #: xpack/plugins/change_auth_plan/serializers/asset.py:173 msgid "Is success" -msgstr "" +msgstr "是否成功" #: assets/models/backup.py:99 msgid "Account backup execution" -msgstr "" +msgstr "账号备份执行" #: assets/models/base.py:26 msgid "Connectivity" -msgstr "" +msgstr "可连接性" #: assets/models/base.py:28 authentication/models/temp_token.py:12 msgid "Date verified" -msgstr "" +msgstr "校验日期" #: assets/models/base.py:70 msgid "Privileged" @@ -981,229 +1030,243 @@ msgstr "" #: assets/models/cmd_filter.py:33 perms/models/asset_permission.py:56 #: users/models/group.py:31 users/models/user.py:681 msgid "User group" -msgstr "" +msgstr "用户组" #: assets/models/cmd_filter.py:57 msgid "Command filter" -msgstr "" +msgstr "命令过滤器" #: assets/models/cmd_filter.py:71 msgid "Deny" -msgstr "" +msgstr "拒绝" #: assets/models/cmd_filter.py:72 msgid "Allow" -msgstr "" +msgstr "允许" #: assets/models/cmd_filter.py:73 msgid "Reconfirm" -msgstr "" +msgstr "复核" #: assets/models/cmd_filter.py:77 msgid "Filter" -msgstr "" +msgstr "过滤器" #: assets/models/cmd_filter.py:100 msgid "Command filter rule" -msgstr "" +msgstr "命令过滤规则" #: assets/models/gateway.py:61 authentication/models/connection_token.py:101 +#, fuzzy msgid "No account" -msgstr "" +msgstr "账号" #: assets/models/gateway.py:83 -#, python-brace-format +#, fuzzy, python-brace-format msgid "Unable to connect to port {port} on {address}" -msgstr "" +msgstr "无法连接到 {ip} 上的端口 {port}" #: assets/models/gateway.py:86 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" -msgstr "" +msgstr "认证失败" #: assets/models/gateway.py:88 assets/models/gateway.py:115 msgid "Connect failed" -msgstr "" +msgstr "连接失败" #: assets/models/gathered_user.py:16 msgid "Present" -msgstr "" +msgstr "存在" #: assets/models/gathered_user.py:17 msgid "Date last login" -msgstr "" +msgstr "最后登录日期" #: assets/models/gathered_user.py:18 msgid "IP last login" -msgstr "" +msgstr "最后登录IP" #: assets/models/gathered_user.py:31 msgid "GatherUser" -msgstr "" +msgstr "收集用户" #: assets/models/group.py:30 msgid "Asset group" -msgstr "" +msgstr "资产组" #: assets/models/group.py:34 assets/models/platform.py:19 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" -msgstr "" +msgstr "默认" #: assets/models/group.py:34 msgid "Default asset group" -msgstr "" +msgstr "默认资产组" #: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:924 msgid "System" -msgstr "" +msgstr "系统" #: assets/models/label.py:18 assets/models/node.py:553 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 #: authentication/models/connection_token.py:22 #: common/drf/serializers/common.py:82 settings/models.py:34 msgid "Value" -msgstr "" +msgstr "值" #: assets/models/label.py:36 assets/serializers/cagegory.py:6 #: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 #: settings/serializers/sms.py:7 msgid "Label" -msgstr "" +msgstr "标签" #: assets/models/node.py:158 msgid "New node" -msgstr "" +msgstr "新节点" #: assets/models/node.py:481 msgid "empty" -msgstr "" +msgstr "空" #: assets/models/node.py:552 perms/models/perm_node.py:21 msgid "Key" -msgstr "" +msgstr "键" #: assets/models/node.py:554 assets/serializers/node.py:20 msgid "Full value" -msgstr "" +msgstr "全称" #: assets/models/node.py:558 perms/models/perm_node.py:22 msgid "Parent key" -msgstr "" +msgstr "ssh私钥" #: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 #: xpack/plugins/cloud/serializers/task.py:74 msgid "Node" -msgstr "" +msgstr "节点" #: assets/models/node.py:570 msgid "Can match node" -msgstr "" +msgstr "可以匹配节点" #: assets/models/platform.py:20 +#, fuzzy msgid "Required" -msgstr "" +msgstr "需要 MFA 认证" #: assets/models/platform.py:23 settings/serializers/settings.py:61 #: users/templates/users/reset_password.html:29 msgid "Setting" -msgstr "" +msgstr "设置" #: assets/models/platform.py:42 audits/const.py:69 settings/models.py:37 #: terminal/serializers/applet_host.py:26 msgid "Enabled" -msgstr "" +msgstr "启用" #: assets/models/platform.py:43 msgid "Ansible config" msgstr "" #: assets/models/platform.py:44 +#, fuzzy msgid "Ping enabled" -msgstr "" +msgstr "MFA 已启用" #: assets/models/platform.py:45 msgid "Ping method" msgstr "" #: assets/models/platform.py:46 assets/models/platform.py:56 +#, fuzzy msgid "Gather facts enabled" -msgstr "" +msgstr "收集资产上的用户" #: assets/models/platform.py:47 assets/models/platform.py:58 +#, fuzzy msgid "Gather facts method" -msgstr "" +msgstr "收集资产上的用户" #: assets/models/platform.py:48 +#, fuzzy msgid "Push account enabled" -msgstr "" +msgstr "MFA 多因子认证没有开启" #: assets/models/platform.py:49 msgid "Push account method" msgstr "" #: assets/models/platform.py:50 +#, fuzzy msgid "Change password enabled" -msgstr "" +msgstr "更改密码" #: assets/models/platform.py:52 +#, fuzzy msgid "Change password method" -msgstr "" +msgstr "更改密码" #: assets/models/platform.py:53 +#, fuzzy msgid "Verify account enabled" -msgstr "" +msgstr "服务账号密钥" #: assets/models/platform.py:55 +#, fuzzy msgid "Verify account method" -msgstr "" +msgstr "验证密码/密钥" #: assets/models/platform.py:75 tickets/models/ticket/general.py:299 msgid "Meta" -msgstr "" +msgstr "元数据" #: assets/models/platform.py:76 msgid "Internal" -msgstr "" +msgstr "内部的" #: assets/models/platform.py:80 assets/serializers/platform.py:97 msgid "Charset" -msgstr "" +msgstr "编码" #: assets/models/platform.py:82 +#, fuzzy msgid "Domain enabled" -msgstr "" +msgstr "网域名称" #: assets/models/platform.py:83 +#, fuzzy msgid "Protocols enabled" -msgstr "" +msgstr "协议组" #: assets/models/platform.py:85 +#, fuzzy msgid "Su enabled" -msgstr "" +msgstr "MFA 已启用" #: assets/models/platform.py:86 msgid "SU method" msgstr "" #: assets/models/platform.py:88 assets/serializers/platform.py:104 +#, fuzzy msgid "Automation" -msgstr "" +msgstr "托管密码" #: assets/models/utils.py:19 #, python-format msgid "%(value)s is not an even number" -msgstr "" +msgstr "%(value)s is not an even number" #: assets/notifications.py:8 msgid "Notification of account backup route task results" -msgstr "" +msgstr "账号备份任务结果通知" #: assets/notifications.py:18 msgid "" "{} - The account backup passage task has been completed. See the attachment " "for details" -msgstr "" +msgstr "{} - 账号备份任务已完成, 详情见附件" #: assets/notifications.py:20 msgid "" @@ -1211,17 +1274,19 @@ msgid "" "password has not been set - please go to personal information -> file " "encryption password to set the encryption password" msgstr "" +"{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" +"置加密密码" #: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 msgid "Notification of implementation result of encryption change plan" -msgstr "" +msgstr "改密计划任务结果通知" #: assets/notifications.py:41 #: xpack/plugins/change_auth_plan/notifications.py:18 msgid "" "{} - The encryption change task has been completed. See the attachment for " "details" -msgstr "" +msgstr "{} - 改密任务已完成, 详情见附件" #: assets/notifications.py:42 #: xpack/plugins/change_auth_plan/notifications.py:19 @@ -1230,14 +1295,17 @@ msgid "" "has not been set - please go to personal information -> file encryption " "password to set the encryption password" msgstr "" +"{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" +"密密码" #: assets/serializers/account/account.py:18 msgid "Push now" msgstr "" #: assets/serializers/account/account.py:20 +#, fuzzy msgid "Has secret" -msgstr "" +msgstr "密钥" #: assets/serializers/account/account.py:27 msgid "Account template not found" @@ -1248,13 +1316,13 @@ msgstr "" #: settings/serializers/auth/ldap.py:66 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" -msgstr "" +msgstr "定时执行" #: assets/serializers/account/backup.py:31 #: assets/serializers/automations/change_secret.py:41 #: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" -msgstr "" +msgstr "当前只支持邮件发送" #: assets/serializers/asset/common.py:68 assets/serializers/platform.py:102 #: authentication/serializers/connect_token_secret.py:27 @@ -1262,95 +1330,97 @@ msgstr "" #: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 #: xpack/plugins/cloud/serializers/task.py:43 msgid "Protocols" -msgstr "" +msgstr "协议组" #: assets/serializers/asset/common.py:88 msgid "Address" -msgstr "" +msgstr "地址" #: assets/serializers/asset/common.py:156 +#, fuzzy msgid "Platform not exist" -msgstr "" +msgstr "应用不存在" #: assets/serializers/asset/common.py:172 +#, fuzzy msgid "Protocol is required: {}" -msgstr "" +msgstr "协议重复: {}" #: assets/serializers/asset/host.py:12 msgid "Vendor" -msgstr "" +msgstr "制造商" #: assets/serializers/asset/host.py:13 msgid "Model" -msgstr "" +msgstr "型号" #: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 msgid "Serial number" -msgstr "" +msgstr "序列号" #: assets/serializers/asset/host.py:16 msgid "CPU model" -msgstr "" +msgstr "CPU型号" #: assets/serializers/asset/host.py:17 msgid "CPU count" -msgstr "" +msgstr "CPU数量" #: assets/serializers/asset/host.py:18 msgid "CPU cores" -msgstr "" +msgstr "CPU核数" #: assets/serializers/asset/host.py:19 msgid "CPU vcpus" -msgstr "" +msgstr "CPU总数" #: assets/serializers/asset/host.py:20 msgid "Memory" -msgstr "" +msgstr "内存" #: assets/serializers/asset/host.py:21 msgid "Disk total" -msgstr "" +msgstr "硬盘大小" #: assets/serializers/asset/host.py:22 msgid "Disk info" -msgstr "" +msgstr "硬盘信息" #: assets/serializers/asset/host.py:24 msgid "OS" -msgstr "" +msgstr "操作系统" #: assets/serializers/asset/host.py:25 msgid "OS version" -msgstr "" +msgstr "系统版本" #: assets/serializers/asset/host.py:26 msgid "OS arch" -msgstr "" +msgstr "系统架构" #: assets/serializers/asset/host.py:27 msgid "Hostname raw" -msgstr "" +msgstr "主机名原始" #: assets/serializers/asset/host.py:28 msgid "Asset number" -msgstr "" +msgstr "资产编号" #: assets/serializers/automations/change_secret.py:28 #: xpack/plugins/change_auth_plan/models/asset.py:50 #: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "SSH Key strategy" -msgstr "" +msgstr "SSH 密钥策略" #: assets/serializers/automations/change_secret.py:70 #: xpack/plugins/change_auth_plan/serializers/base.py:58 msgid "* Please enter the correct password length" -msgstr "" +msgstr "* 请输入正确的密码长度" #: assets/serializers/automations/change_secret.py:73 #: xpack/plugins/change_auth_plan/serializers/base.py:61 msgid "* Password length range 6-30 bits" -msgstr "" +msgstr "* 密码长度范围 6-30 位" #: assets/serializers/automations/change_secret.py:117 #: assets/serializers/automations/change_secret.py:145 audits/const.py:74 @@ -1358,63 +1428,68 @@ msgstr "" #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" -msgstr "" +msgstr "成功" #: assets/serializers/automations/gather_accounts.py:23 +#, fuzzy msgid "Executed amount" -msgstr "" +msgstr "执行次数" #: assets/serializers/base.py:21 msgid "Key password" -msgstr "" +msgstr "密钥密码" #: assets/serializers/cagegory.py:9 msgid "Constraints" msgstr "" #: assets/serializers/cagegory.py:15 +#, fuzzy msgid "Types" -msgstr "" +msgstr "类型" #: assets/serializers/domain.py:16 msgid "Gateway" -msgstr "" +msgstr "网关" #: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:9 msgid "Hostname" -msgstr "" +msgstr "主机名" #: assets/serializers/label.py:12 msgid "Assets amount" -msgstr "" +msgstr "资产数量" #: assets/serializers/label.py:13 msgid "Category display" -msgstr "" +msgstr "类别名称" #: assets/serializers/node.py:17 msgid "value" -msgstr "" +msgstr "值" #: assets/serializers/node.py:31 msgid "Can't contains: /" -msgstr "" +msgstr "不能包含: /" #: assets/serializers/node.py:41 msgid "The same level node name cannot be the same" -msgstr "" +msgstr "同级别节点名字不能重复" #: assets/serializers/platform.py:24 +#, fuzzy msgid "SFTP enabled" -msgstr "" +msgstr "MFA 已启用" #: assets/serializers/platform.py:25 +#, fuzzy msgid "SFTP home" -msgstr "" +msgstr "SFTP根路径" #: assets/serializers/platform.py:28 +#, fuzzy msgid "Auto fill" -msgstr "" +msgstr "自动" #: assets/serializers/platform.py:79 msgid "Primary" @@ -1422,43 +1497,47 @@ msgstr "" #: assets/serializers/utils.py:13 msgid "Password can not contains `{{` " -msgstr "" +msgstr "密码不能包含 `{{` 字符" #: assets/serializers/utils.py:16 msgid "Password can not contains `'` " -msgstr "" +msgstr "密码不能包含 `'` 字符" #: assets/serializers/utils.py:18 msgid "Password can not contains `\"` " -msgstr "" +msgstr "密码不能包含 `\"` 字符" #: assets/serializers/utils.py:24 msgid "private key invalid or passphrase error" -msgstr "" +msgstr "密钥不合法或密钥密码错误" #: assets/tasks/automation.py:11 +#, fuzzy msgid "Execute automation" -msgstr "" +msgstr "执行批量命令" #: assets/tasks/backup.py:13 +#, fuzzy msgid "Execute account backup plan" -msgstr "" +msgstr "账号备份计划" #: assets/tasks/gather_accounts.py:31 +#, fuzzy msgid "Gather assets accounts" -msgstr "" +msgstr "收集资产上的用户" #: assets/tasks/gather_facts.py:26 msgid "Update some assets hardware info. " -msgstr "" +msgstr "更新资产硬件信息. " #: assets/tasks/gather_facts.py:44 +#, fuzzy msgid "Manually update the hardware information of assets" -msgstr "" +msgstr "更新节点资产硬件信息: " #: assets/tasks/gather_facts.py:49 msgid "Update assets hardware info: " -msgstr "" +msgstr "更新资产硬件信息: " #: assets/tasks/gather_facts.py:53 msgid "Manually update the hardware information of assets under a node" @@ -1466,7 +1545,7 @@ msgstr "" #: assets/tasks/gather_facts.py:59 msgid "Update node asset hardware information: " -msgstr "" +msgstr "更新节点资产硬件信息: " #: assets/tasks/nodes_amount.py:16 msgid "Check the amount of assets under the node" @@ -1475,270 +1554,275 @@ msgstr "" #: assets/tasks/nodes_amount.py:28 msgid "" "The task of self-checking is already running and cannot be started repeatedly" -msgstr "" +msgstr "自检程序已经在运行,不能重复启动" #: assets/tasks/nodes_amount.py:34 msgid "Periodic check the amount of assets under the node" msgstr "" #: assets/tasks/ping.py:21 assets/tasks/ping.py:39 +#, fuzzy msgid "Test assets connectivity " -msgstr "" +msgstr "测试资产可连接性. " #: assets/tasks/ping.py:33 +#, fuzzy msgid "Manually test the connectivity of a asset" -msgstr "" +msgstr "可以测试资产连接性" #: assets/tasks/ping.py:43 msgid "Manually test the connectivity of assets under a node" msgstr "" #: assets/tasks/ping.py:49 +#, fuzzy msgid "Test if the assets under the node are connectable " -msgstr "" +msgstr "测试节点下资产是否可连接: " #: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 +#, fuzzy msgid "Push accounts to assets" -msgstr "" +msgstr "推送系统用户到入资产: " #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" -msgstr "" +msgstr "资产已经被禁用, 跳过: {}" #: assets/tasks/utils.py:21 msgid "Asset may not be support ansible, skipped: {}" -msgstr "" +msgstr "资产或许不支持ansible, 跳过: {}" #: assets/tasks/utils.py:39 msgid "For security, do not push user {}" -msgstr "" +msgstr "为了安全,禁止推送用户 {}" #: assets/tasks/utils.py:55 msgid "No assets matched, stop task" -msgstr "" +msgstr "没有匹配到资产,结束任务" #: assets/tasks/verify_account.py:30 msgid "Verify asset account availability" msgstr "" #: assets/tasks/verify_account.py:37 +#, fuzzy msgid "Verify accounts connectivity" -msgstr "" +msgstr "测试账号可连接性: " #: audits/apps.py:9 msgid "Audits" -msgstr "" +msgstr "日志审计" #: audits/backends/db.py:12 msgid "The text content is too long. Use Elasticsearch to store operation logs" -msgstr "" +msgstr "文字内容太长。请使用 Elasticsearch 存储操作日志" #: audits/backends/db.py:24 audits/backends/db.py:26 msgid "Tips" -msgstr "" +msgstr "提示" #: audits/const.py:45 msgid "Mkdir" -msgstr "" +msgstr "创建目录" #: audits/const.py:46 msgid "Rmdir" -msgstr "" +msgstr "删除目录" #: audits/const.py:47 audits/const.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:226 msgid "Delete" -msgstr "" +msgstr "删除" #: audits/const.py:48 perms/const.py:13 msgid "Upload" -msgstr "" +msgstr "上传文件" #: audits/const.py:49 msgid "Rename" -msgstr "" +msgstr "重命名" #: audits/const.py:50 msgid "Symlink" -msgstr "" +msgstr "建立软链接" #: audits/const.py:51 perms/const.py:14 msgid "Download" -msgstr "" +msgstr "下载文件" #: audits/const.py:55 rbac/tree.py:224 msgid "View" -msgstr "" +msgstr "查看" #: audits/const.py:56 rbac/tree.py:225 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" -msgstr "" +msgstr "更新" #: audits/const.py:58 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:223 msgid "Create" -msgstr "" +msgstr "创建" #: audits/const.py:63 settings/serializers/terminal.py:6 #: terminal/models/applet/host.py:24 terminal/models/component/terminal.py:159 msgid "Terminal" -msgstr "" +msgstr "终端" #: audits/const.py:70 msgid "-" -msgstr "" +msgstr "-" #: audits/handler.py:134 msgid "Yes" -msgstr "" +msgstr "是" #: audits/handler.py:134 msgid "No" -msgstr "" +msgstr "否" #: audits/models.py:32 audits/models.py:55 audits/models.py:96 #: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 msgid "Remote addr" -msgstr "" +msgstr "远端地址" #: audits/models.py:37 audits/serializers.py:19 msgid "Operate" -msgstr "" +msgstr "操作" #: audits/models.py:39 msgid "Filename" -msgstr "" +msgstr "文件名" #: audits/models.py:44 msgid "File transfer log" -msgstr "" +msgstr "文件管理" #: audits/models.py:53 audits/serializers.py:91 msgid "Resource Type" -msgstr "" +msgstr "资源类型" #: audits/models.py:54 msgid "Resource" -msgstr "" +msgstr "资源" #: audits/models.py:56 audits/models.py:98 #: terminal/backends/command/serializers.py:41 msgid "Datetime" -msgstr "" +msgstr "日期" #: audits/models.py:88 msgid "Operate log" -msgstr "" +msgstr "操作日志" #: audits/models.py:94 msgid "Change by" -msgstr "" +msgstr "修改者" #: audits/models.py:104 msgid "Password change log" -msgstr "" +msgstr "改密日志" #: audits/models.py:111 msgid "Login type" -msgstr "" +msgstr "登录方式" #: audits/models.py:113 tickets/models/ticket/login_confirm.py:10 msgid "Login ip" -msgstr "" +msgstr "登录IP" #: audits/models.py:115 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/models/ticket/login_confirm.py:11 msgid "Login city" -msgstr "" +msgstr "登录城市" #: audits/models.py:118 audits/serializers.py:62 msgid "User agent" -msgstr "" +msgstr "用户代理" #: audits/models.py:121 audits/serializers.py:39 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:698 #: users/serializers/profile.py:126 msgid "MFA" -msgstr "" +msgstr "MFA" #: audits/models.py:131 msgid "Date login" -msgstr "" +msgstr "登录日期" #: audits/models.py:133 audits/serializers.py:64 msgid "Authentication backend" -msgstr "" +msgstr "认证方式" #: audits/models.py:174 msgid "User login log" -msgstr "" +msgstr "用户登录日志" #: audits/serializers.py:63 msgid "Reason display" -msgstr "" +msgstr "原因描述" #: audits/signal_handlers.py:48 msgid "SSH Key" -msgstr "" +msgstr "SSH 密钥" #: audits/signal_handlers.py:50 settings/serializers/auth/sso.py:10 msgid "SSO" -msgstr "" +msgstr "SSO" #: audits/signal_handlers.py:51 msgid "Auth Token" -msgstr "" +msgstr "认证令牌" #: audits/signal_handlers.py:52 authentication/notifications.py:73 #: authentication/views/login.py:73 authentication/views/wecom.py:178 #: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 #: users/models/user.py:736 msgid "WeCom" -msgstr "" +msgstr "企业微信" #: audits/signal_handlers.py:53 authentication/views/feishu.py:145 #: authentication/views/login.py:85 notifications/backends/__init__.py:14 #: settings/serializers/auth/feishu.py:10 users/models/user.py:738 msgid "FeiShu" -msgstr "" +msgstr "飞书" #: audits/signal_handlers.py:54 authentication/views/dingtalk.py:180 #: authentication/views/login.py:79 notifications/backends/__init__.py:12 #: settings/serializers/auth/dingtalk.py:10 users/models/user.py:737 msgid "DingTalk" -msgstr "" +msgstr "钉钉" #: audits/signal_handlers.py:55 authentication/models/temp_token.py:16 msgid "Temporary token" -msgstr "" +msgstr "临时密码" #: authentication/api/confirm.py:40 msgid "This action require verify your MFA" -msgstr "" +msgstr "此操作需要验证您的 MFA" #: authentication/api/mfa.py:59 msgid "Current user not support mfa type: {}" -msgstr "" +msgstr "当前用户不支持 MFA 类型: {}" #: authentication/api/password.py:31 terminal/api/session/session.py:225 #: users/views/profile/reset.py:44 msgid "User does not exist: {}" -msgstr "" +msgstr "用户不存在: {}" #: authentication/api/password.py:31 users/views/profile/reset.py:127 msgid "No user matched" -msgstr "" +msgstr "没有匹配到用户" #: authentication/api/password.py:35 msgid "" "The user is from {}, please go to the corresponding system to change the " "password" -msgstr "" +msgstr "用户来自 {} 请去相应系统修改密码" #: authentication/api/password.py:59 #: authentication/templates/authentication/login.html:256 @@ -1747,7 +1831,7 @@ msgstr "" #: users/templates/users/forgot_password_previewing.html:13 #: users/templates/users/forgot_password_previewing.html:14 msgid "Forgot password" -msgstr "" +msgstr "忘记密码" #: authentication/apps.py:7 settings/serializers/auth/base.py:10 #: settings/serializers/auth/cas.py:10 settings/serializers/auth/dingtalk.py:10 @@ -1756,123 +1840,123 @@ msgstr "" #: settings/serializers/auth/radius.py:13 settings/serializers/auth/saml2.py:11 #: settings/serializers/auth/sso.py:10 settings/serializers/auth/wecom.py:10 msgid "Authentication" -msgstr "" +msgstr "认证" #: authentication/backends/custom.py:58 #: authentication/backends/oauth2/backends.py:158 msgid "User invalid, disabled or expired" -msgstr "" +msgstr "用户无效,已禁用或已过期" #: authentication/backends/drf.py:56 msgid "Invalid signature header. No credentials provided." -msgstr "" +msgstr "不合法的签名头" #: authentication/backends/drf.py:59 msgid "Invalid signature header. Signature string should not contain spaces." -msgstr "" +msgstr "不合法的签名头" #: authentication/backends/drf.py:66 msgid "Invalid signature header. Format like AccessKeyId:Signature" -msgstr "" +msgstr "不合法的签名头" #: authentication/backends/drf.py:70 msgid "" "Invalid signature header. Signature string should not contain invalid " "characters." -msgstr "" +msgstr "不合法的签名头" #: authentication/backends/drf.py:90 authentication/backends/drf.py:106 msgid "Invalid signature." -msgstr "" +msgstr "签名无效" #: authentication/backends/drf.py:97 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" -msgstr "" +msgstr "HTTP header not valid" #: authentication/backends/drf.py:102 msgid "Expired, more than 15 minutes" -msgstr "" +msgstr "已过期,超过15分钟" #: authentication/backends/drf.py:109 msgid "User disabled." -msgstr "" +msgstr "用户已禁用" #: authentication/backends/drf.py:127 msgid "Invalid token header. No credentials provided." -msgstr "" +msgstr "无效的令牌头。没有提供任何凭据。" #: authentication/backends/drf.py:130 msgid "Invalid token header. Sign string should not contain spaces." -msgstr "" +msgstr "无效的令牌头。符号字符串不应包含空格。" #: authentication/backends/drf.py:137 msgid "" "Invalid token header. Sign string should not contain invalid characters." -msgstr "" +msgstr "无效的令牌头。符号字符串不应包含无效字符。" #: authentication/backends/drf.py:148 msgid "Invalid token or cache refreshed." -msgstr "" +msgstr "刷新的令牌或缓存无效。" #: authentication/confirm/password.py:16 msgid "Authentication failed password incorrect" -msgstr "" +msgstr "认证失败 (用户名或密码不正确)" #: authentication/confirm/relogin.py:10 msgid "Login time has exceeded {} minutes, please login again" -msgstr "" +msgstr "登录时长已超过 {} 分钟,请重新登录" #: authentication/errors/const.py:18 msgid "Username/password check failed" -msgstr "" +msgstr "用户名/密码 校验失败" #: authentication/errors/const.py:19 msgid "Password decrypt failed" -msgstr "" +msgstr "密码解密失败" #: authentication/errors/const.py:20 msgid "MFA failed" -msgstr "" +msgstr "MFA 校验失败" #: authentication/errors/const.py:21 msgid "MFA unset" -msgstr "" +msgstr "MFA 没有设定" #: authentication/errors/const.py:22 msgid "Username does not exist" -msgstr "" +msgstr "用户名不存在" #: authentication/errors/const.py:23 msgid "Password expired" -msgstr "" +msgstr "密码已过期" #: authentication/errors/const.py:24 msgid "Disabled or expired" -msgstr "" +msgstr "禁用或失效" #: authentication/errors/const.py:25 msgid "This account is inactive." -msgstr "" +msgstr "此账号已禁用" #: authentication/errors/const.py:26 msgid "This account is expired" -msgstr "" +msgstr "此账号已过期" #: authentication/errors/const.py:27 msgid "Auth backend not match" -msgstr "" +msgstr "没有匹配到认证后端" #: authentication/errors/const.py:28 msgid "ACL is not allowed" -msgstr "" +msgstr "登录访问控制不被允许" #: authentication/errors/const.py:29 msgid "Only local users are allowed" -msgstr "" +msgstr "仅允许本地用户" #: authentication/errors/const.py:39 msgid "No session found, check your cookie" -msgstr "" +msgstr "会话已变更,刷新页面" #: authentication/errors/const.py:41 #, python-brace-format @@ -1881,18 +1965,21 @@ msgid "" "You can also try {times_try} times (The account will be temporarily locked " "for {block_time} minutes)" msgstr "" +"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将" +"被临时 锁定 {block_time} 分钟)" #: authentication/errors/const.py:47 authentication/errors/const.py:55 msgid "" "The account has been locked (please contact admin to unlock it or try again " "after {} minutes)" -msgstr "" +msgstr "账号已被锁定(请联系管理员解锁或{}分钟后重试)" #: authentication/errors/const.py:51 +#, fuzzy msgid "" "The address has been locked (please contact admin to unlock it or try again " "after {} minutes)" -msgstr "" +msgstr "IP 已被锁定(请联系管理员解锁或{}分钟后重试)" #: authentication/errors/const.py:59 #, python-brace-format @@ -1900,153 +1987,154 @@ msgid "" "{error}, You can also try {times_try} times (The account will be temporarily " "locked for {block_time} minutes)" msgstr "" +"{error},您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)" #: authentication/errors/const.py:63 msgid "MFA required" -msgstr "" +msgstr "需要 MFA 认证" #: authentication/errors/const.py:64 msgid "MFA not set, please set it first" -msgstr "" +msgstr "MFA 没有设置,请先完成设置" #: authentication/errors/const.py:65 msgid "Login confirm required" -msgstr "" +msgstr "需要登录复核" #: authentication/errors/const.py:66 msgid "Wait login confirm ticket for accept" -msgstr "" +msgstr "等待登录复核处理" #: authentication/errors/const.py:67 msgid "Login confirm ticket was {}" -msgstr "" +msgstr "登录复核: {}" #: authentication/errors/failed.py:146 msgid "Current IP and Time period is not allowed" -msgstr "" +msgstr "当前 IP 和时间段不被允许登录" #: authentication/errors/failed.py:151 msgid "Please enter MFA code" -msgstr "" +msgstr "请输入 MFA 验证码" #: authentication/errors/failed.py:156 msgid "Please enter SMS code" -msgstr "" +msgstr "请输入短信验证码" #: authentication/errors/failed.py:161 users/exceptions.py:15 msgid "Phone not set" -msgstr "" +msgstr "手机号没有设置" #: authentication/errors/mfa.py:8 msgid "SSO auth closed" -msgstr "" +msgstr "SSO 认证关闭了" #: authentication/errors/mfa.py:18 authentication/views/wecom.py:80 msgid "WeCom is already bound" -msgstr "" +msgstr "企业微信已经绑定" #: authentication/errors/mfa.py:23 authentication/views/wecom.py:237 #: authentication/views/wecom.py:291 msgid "WeCom is not bound" -msgstr "" +msgstr "没有绑定企业微信" #: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 #: authentication/views/dingtalk.py:297 msgid "DingTalk is not bound" -msgstr "" +msgstr "钉钉没有绑定" #: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 msgid "FeiShu is not bound" -msgstr "" +msgstr "没有绑定飞书" #: authentication/errors/mfa.py:38 msgid "Your password is invalid" -msgstr "" +msgstr "您的密码无效" #: authentication/errors/redirect.py:85 authentication/mixins.py:306 msgid "Your password is too simple, please change it for security" -msgstr "" +msgstr "你的密码过于简单,为了安全,请修改" #: authentication/errors/redirect.py:93 authentication/mixins.py:313 msgid "You should to change your password before login" -msgstr "" +msgstr "登录完成前,请先修改密码" #: authentication/errors/redirect.py:101 authentication/mixins.py:320 msgid "Your password has expired, please reset before logging in" -msgstr "" +msgstr "您的密码已过期,先修改再登录" #: authentication/forms.py:45 msgid "{} days auto login" -msgstr "" +msgstr "{} 天内自动登录" #: authentication/forms.py:56 msgid "MFA Code" -msgstr "" +msgstr "MFA 验证码" #: authentication/forms.py:57 msgid "MFA type" -msgstr "" +msgstr "MFA 类型" #: authentication/forms.py:65 #: authentication/templates/authentication/_captcha_field.html:15 msgid "Captcha" -msgstr "" +msgstr "验证码" #: authentication/forms.py:70 users/forms/profile.py:28 msgid "MFA code" -msgstr "" +msgstr "MFA 验证码" #: authentication/forms.py:72 msgid "Dynamic code" -msgstr "" +msgstr "动态码" #: authentication/mfa/base.py:7 msgid "Please input security code" -msgstr "" +msgstr "请输入动态安全码" #: authentication/mfa/custom.py:20 msgid "MFA Custom code invalid" -msgstr "" +msgstr "自定义 MFA 验证码校验失败" #: authentication/mfa/custom.py:26 msgid "MFA custom verification code" -msgstr "" +msgstr "自定义 MFA 验证码" #: authentication/mfa/custom.py:56 msgid "MFA custom global enabled, cannot disable" -msgstr "" +msgstr "自定义 MFA 全局开启,无法被禁用" #: authentication/mfa/otp.py:7 msgid "OTP code invalid, or server time error" -msgstr "" +msgstr "虚拟 MFA 验证码错误,或者服务器端时间不对" #: authentication/mfa/otp.py:12 msgid "OTP" -msgstr "" +msgstr "虚拟 MFA" #: authentication/mfa/otp.py:13 msgid "OTP verification code" -msgstr "" +msgstr "虚拟 MFA 验证码" #: authentication/mfa/otp.py:48 msgid "Virtual OTP based MFA" -msgstr "" +msgstr "虚拟 MFA(OTP)" #: authentication/mfa/radius.py:7 msgid "Radius verify code invalid" -msgstr "" +msgstr "Radius 校验失败" #: authentication/mfa/radius.py:13 msgid "Radius verification code" -msgstr "" +msgstr "Radius 动态安全码" #: authentication/mfa/radius.py:44 msgid "Radius global enabled, cannot disable" -msgstr "" +msgstr "Radius MFA 全局开启,无法被禁用" #: authentication/mfa/sms.py:7 msgid "SMS verify code invalid" -msgstr "" +msgstr "短信验证码校验失败" #: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 @@ -2054,122 +2142,127 @@ msgstr "" #: users/forms/profile.py:106 users/templates/users/forgot_password.html:111 #: users/views/profile/reset.py:79 msgid "SMS" -msgstr "" +msgstr "短信" #: authentication/mfa/sms.py:13 msgid "SMS verification code" -msgstr "" +msgstr "短信验证码" #: authentication/mfa/sms.py:57 msgid "Set phone number to enable" -msgstr "" +msgstr "设置手机号码启用" #: authentication/mfa/sms.py:61 msgid "Clear phone number to disable" -msgstr "" +msgstr "清空手机号码禁用" #: authentication/middleware.py:77 settings/utils/ldap.py:652 msgid "Authentication failed (before login check failed): {}" -msgstr "" +msgstr "认证失败(登录前检查失败): {}" #: authentication/mixins.py:256 msgid "The MFA type ({}) is not enabled" -msgstr "" +msgstr "该 MFA ({}) 方式没有启用" #: authentication/mixins.py:296 msgid "Please change your password" -msgstr "" +msgstr "请修改密码" #: authentication/models/connection_token.py:31 #: terminal/serializers/storage.py:111 msgid "Account name" -msgstr "" +msgstr "账号名称" #: authentication/models/connection_token.py:32 +#, fuzzy msgid "Input username" -msgstr "" +msgstr "自定义用户名" #: authentication/models/connection_token.py:33 +#, fuzzy msgid "Input secret" -msgstr "" +msgstr "客户端密钥" #: authentication/models/connection_token.py:35 #: authentication/serializers/connect_token_secret.py:110 #: perms/models/perm_token.py:17 +#, fuzzy msgid "Connect method" -msgstr "" +msgstr "连接超时时间" #: authentication/models/connection_token.py:36 #: rbac/serializers/rolebinding.py:21 msgid "User display" -msgstr "" +msgstr "用户名称" #: authentication/models/connection_token.py:37 msgid "Asset display" -msgstr "" +msgstr "资产名称" #: authentication/models/connection_token.py:38 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 #: tickets/models/ticket/apply_application.py:31 #: tickets/models/ticket/apply_asset.py:20 users/models/user.py:719 msgid "Date expired" -msgstr "" +msgstr "失效日期" #: authentication/models/connection_token.py:42 msgid "Connection token" -msgstr "" +msgstr "连接令牌" #: authentication/models/connection_token.py:44 msgid "Can view connection token secret" -msgstr "" +msgstr "可以查看连接令牌密文" #: authentication/models/connection_token.py:91 msgid "Connection token expired at: {}" -msgstr "" +msgstr "连接令牌过期: {}" #: authentication/models/connection_token.py:94 msgid "No user or invalid user" msgstr "" #: authentication/models/connection_token.py:98 +#, fuzzy msgid "No asset or inactive asset" -msgstr "" +msgstr "资产未激活" #: authentication/models/connection_token.py:173 msgid "Super connection token" -msgstr "" +msgstr "超级连接令牌" #: authentication/models/private_token.py:9 msgid "Private Token" -msgstr "" +msgstr "SSH密钥" #: authentication/models/sso_token.py:15 msgid "Expired" -msgstr "" +msgstr "过期时间" #: authentication/models/sso_token.py:20 msgid "SSO token" -msgstr "" +msgstr "SSO token" #: authentication/models/temp_token.py:11 msgid "Verified" -msgstr "" +msgstr "已校验" #: authentication/notifications.py:19 msgid "Different city login reminder" -msgstr "" +msgstr "异地登录提醒" #: authentication/notifications.py:52 msgid "binding reminder" -msgstr "" +msgstr "绑定提醒" #: authentication/serializers/connect_token_secret.py:109 +#, fuzzy msgid "Expired now" -msgstr "" +msgstr "过期时间" #: authentication/serializers/connection_token.py:14 msgid "Expired time" -msgstr "" +msgstr "过期时间" #: authentication/serializers/password_mfa.py:16 #: authentication/serializers/password_mfa.py:24 @@ -2179,90 +2272,90 @@ msgstr "" #: users/templates/users/forgot_password.html:116 #: users/views/profile/reset.py:73 msgid "Email" -msgstr "" +msgstr "邮箱" #: authentication/serializers/password_mfa.py:29 #: users/templates/users/forgot_password.html:107 msgid "The {} cannot be empty" -msgstr "" +msgstr "{} 不能为空" #: authentication/serializers/token.py:79 perms/serializers/permission.py:30 #: perms/serializers/permission.py:61 users/serializers/user.py:203 msgid "Is valid" -msgstr "" +msgstr "账号是否有效" #: authentication/templates/authentication/_access_key_modal.html:6 msgid "API key list" -msgstr "" +msgstr "API Key列表" #: authentication/templates/authentication/_access_key_modal.html:18 msgid "Using api key sign api header, every requests header difference" -msgstr "" +msgstr "使用api key签名请求头,每个请求的头部是不一样的" #: authentication/templates/authentication/_access_key_modal.html:19 msgid "docs" -msgstr "" +msgstr "文档" #: authentication/templates/authentication/_access_key_modal.html:30 #: users/serializers/group.py:35 msgid "ID" -msgstr "" +msgstr "ID" #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:96 terminal/notifications.py:144 msgid "Date" -msgstr "" +msgstr "日期" #: authentication/templates/authentication/_access_key_modal.html:48 msgid "Show" -msgstr "" +msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 #: settings/serializers/security.py:39 users/models/user.py:559 #: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" -msgstr "" +msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 #: users/models/user.py:560 users/serializers/profile.py:117 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" -msgstr "" +msgstr "启用" #: authentication/templates/authentication/_access_key_modal.html:147 msgid "Delete success" -msgstr "" +msgstr "删除成功" #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 #: templates/_modal.html:22 tickets/const.py:44 msgid "Close" -msgstr "" +msgstr "关闭" #: authentication/templates/authentication/_captcha_field.html:8 msgid "Play CAPTCHA as audio file" -msgstr "" +msgstr "语言播放验证码" #: authentication/templates/authentication/_mfa_confirm_modal.html:5 msgid "MFA confirm" -msgstr "" +msgstr "MFA 认证校验" #: authentication/templates/authentication/_mfa_confirm_modal.html:17 msgid "Need MFA for view auth" -msgstr "" +msgstr "需要 MFA 认证来查看账号信息" #: authentication/templates/authentication/_mfa_confirm_modal.html:20 #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:37 #: templates/_modal.html:23 templates/flash_message_standalone.html:37 #: users/templates/users/user_password_verify.html:20 msgid "Confirm" -msgstr "" +msgstr "确认" #: authentication/templates/authentication/_mfa_confirm_modal.html:25 msgid "Code error" -msgstr "" +msgstr "代码错误" #: authentication/templates/authentication/_msg_different_city.html:3 #: authentication/templates/authentication/_msg_oauth_bind.html:3 @@ -2279,116 +2372,116 @@ msgstr "" #: users/templates/users/_msg_reset_mfa.html:4 #: users/templates/users/_msg_reset_ssh_key.html:4 msgid "Hello" -msgstr "" +msgstr "你好" #: authentication/templates/authentication/_msg_different_city.html:6 msgid "Your account has remote login behavior, please pay attention" -msgstr "" +msgstr "你的账号存在异地登录行为,请关注。" #: authentication/templates/authentication/_msg_different_city.html:10 msgid "Login time" -msgstr "" +msgstr "登录日期" #: authentication/templates/authentication/_msg_different_city.html:16 msgid "" "If you suspect that the login behavior is abnormal, please modify the " "account password in time." -msgstr "" +msgstr "若怀疑此次登录行为异常,请及时修改账号密码" #: authentication/templates/authentication/_msg_oauth_bind.html:6 msgid "Your account has just been bound to" -msgstr "" +msgstr "您的帐户刚刚绑定到" #: authentication/templates/authentication/_msg_oauth_bind.html:17 msgid "If the operation is not your own, unbind and change the password." -msgstr "" +msgstr "如果操作不是您本人,请解绑并且修改密码" #: authentication/templates/authentication/_msg_reset_password.html:6 msgid "" "Please click the link below to reset your password, if not your request, " "concern your account security" -msgstr "" +msgstr "请点击下面链接重置密码, 如果不是您申请的,请关注账号安全" #: authentication/templates/authentication/_msg_reset_password.html:10 msgid "Click here reset password" -msgstr "" +msgstr "点击这里重置密码" #: authentication/templates/authentication/_msg_reset_password.html:16 #: users/templates/users/_msg_user_created.html:22 msgid "This link is valid for 1 hour. After it expires" -msgstr "" +msgstr "这个链接有效期1小时, 超过时间您可以" #: authentication/templates/authentication/_msg_reset_password.html:17 #: users/templates/users/_msg_user_created.html:23 msgid "request new one" -msgstr "" +msgstr "重新申请" #: authentication/templates/authentication/_msg_reset_password_code.html:12 #: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 #: users/forms/profile.py:104 users/templates/users/forgot_password.html:65 msgid "Verify code" -msgstr "" +msgstr "验证码" #: authentication/templates/authentication/_msg_reset_password_code.html:15 msgid "" "Copy the verification code to the Reset Password page to reset the password." -msgstr "" +msgstr "将验证码复制到重置密码页面,重置密码。" #: authentication/templates/authentication/_msg_reset_password_code.html:18 msgid "The validity period of the verification code is one minute" -msgstr "" +msgstr "验证码有效期为 1 分钟" #: authentication/templates/authentication/_msg_rest_password_success.html:5 msgid "Your password has just been successfully updated" -msgstr "" +msgstr "你的密码刚刚成功更新" #: authentication/templates/authentication/_msg_rest_password_success.html:9 #: authentication/templates/authentication/_msg_rest_public_key_success.html:9 msgid "Browser" -msgstr "" +msgstr "浏览器" #: authentication/templates/authentication/_msg_rest_password_success.html:13 msgid "" "If the password update was not initiated by you, your account may have " "security issues" -msgstr "" +msgstr "如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题" #: authentication/templates/authentication/_msg_rest_password_success.html:14 #: authentication/templates/authentication/_msg_rest_public_key_success.html:14 msgid "If you have any questions, you can contact the administrator" -msgstr "" +msgstr "如果有疑问或需求,请联系系统管理员" #: authentication/templates/authentication/_msg_rest_public_key_success.html:5 msgid "Your public key has just been successfully updated" -msgstr "" +msgstr "你的公钥刚刚成功更新" #: authentication/templates/authentication/_msg_rest_public_key_success.html:13 msgid "" "If the public key update was not initiated by you, your account may have " "security issues" -msgstr "" +msgstr "如果这次公钥更新不是由你发起的,那么你的账号可能存在安全问题" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 #: templates/flash_message_standalone.html:28 tickets/const.py:17 msgid "Cancel" -msgstr "" +msgstr "取消" #: authentication/templates/authentication/login.html:221 msgid "Welcome back, please enter username and password to login" -msgstr "" +msgstr "欢迎回来,请输入用户名和密码登录" #: authentication/templates/authentication/login.html:264 #: templates/_header_bar.html:89 msgid "Login" -msgstr "" +msgstr "登录" #: authentication/templates/authentication/login.html:271 msgid "More login options" -msgstr "" +msgstr "其他方式登录" #: authentication/templates/authentication/login_mfa.html:6 msgid "MFA Auth" -msgstr "" +msgstr "MFA 多因子认证" #: authentication/templates/authentication/login_mfa.html:19 #: users/templates/users/user_otp_check_password.html:12 @@ -2396,234 +2489,237 @@ msgstr "" #: users/templates/users/user_otp_enable_install_app.html:29 #: users/templates/users/user_verify_mfa.html:30 msgid "Next" -msgstr "" +msgstr "下一步" #: authentication/templates/authentication/login_mfa.html:22 msgid "Can't provide security? Please contact the administrator!" -msgstr "" +msgstr "如果不能提供 MFA 验证码,请联系管理员!" #: authentication/templates/authentication/login_wait_confirm.html:41 msgid "Refresh" -msgstr "" +msgstr "刷新" #: authentication/templates/authentication/login_wait_confirm.html:46 msgid "Copy link" -msgstr "" +msgstr "复制链接" #: authentication/templates/authentication/login_wait_confirm.html:51 msgid "Return" -msgstr "" +msgstr "返回" #: authentication/templates/authentication/login_wait_confirm.html:116 msgid "Copy success" -msgstr "" +msgstr "复制成功" #: authentication/utils.py:28 common/utils/ip/geoip/utils.py:24 #: xpack/plugins/cloud/const.py:27 msgid "LAN" -msgstr "" +msgstr "局域网" #: authentication/views/dingtalk.py:42 msgid "DingTalk Error, Please contact your system administrator" -msgstr "" +msgstr "钉钉错误,请联系系统管理员" #: authentication/views/dingtalk.py:45 msgid "DingTalk Error" -msgstr "" +msgstr "钉钉错误" #: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 #: authentication/views/wecom.py:56 msgid "" "The system configuration is incorrect. Please contact your administrator" -msgstr "" +msgstr "企业配置错误,请联系系统管理员" #: authentication/views/dingtalk.py:81 msgid "DingTalk is already bound" -msgstr "" +msgstr "钉钉已经绑定" #: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 msgid "Invalid user_id" -msgstr "" +msgstr "无效的 user_id" #: authentication/views/dingtalk.py:165 msgid "DingTalk query user failed" -msgstr "" +msgstr "钉钉查询用户失败" #: authentication/views/dingtalk.py:174 msgid "The DingTalk is already bound to another user" -msgstr "" +msgstr "该钉钉已经绑定其他用户" #: authentication/views/dingtalk.py:181 msgid "Binding DingTalk successfully" -msgstr "" +msgstr "绑定 钉钉 成功" #: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 msgid "Failed to get user from DingTalk" -msgstr "" +msgstr "从钉钉获取用户失败" #: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 msgid "Please login with a password and then bind the DingTalk" -msgstr "" +msgstr "请使用密码登录,然后绑定钉钉" #: authentication/views/feishu.py:40 msgid "FeiShu Error" -msgstr "" +msgstr "飞书错误" #: authentication/views/feishu.py:88 msgid "FeiShu is already bound" -msgstr "" +msgstr "飞书已经绑定" #: authentication/views/feishu.py:130 msgid "FeiShu query user failed" -msgstr "" +msgstr "飞书查询用户失败" #: authentication/views/feishu.py:139 msgid "The FeiShu is already bound to another user" -msgstr "" +msgstr "该飞书已经绑定其他用户" #: authentication/views/feishu.py:146 msgid "Binding FeiShu successfully" -msgstr "" +msgstr "绑定 飞书 成功" #: authentication/views/feishu.py:198 msgid "Failed to get user from FeiShu" -msgstr "" +msgstr "从飞书获取用户失败" #: authentication/views/feishu.py:205 msgid "Please login with a password and then bind the FeiShu" -msgstr "" +msgstr "请使用密码登录,然后绑定飞书" #: authentication/views/login.py:181 msgid "Redirecting" -msgstr "" +msgstr "跳转中" #: authentication/views/login.py:182 msgid "Redirecting to {} authentication" -msgstr "" +msgstr "正在跳转到 {} 认证" #: authentication/views/login.py:205 msgid "Please enable cookies and try again." -msgstr "" +msgstr "设置你的浏览器支持cookie" #: authentication/views/login.py:307 msgid "" "Wait for {} confirm, You also can copy link to her/him
\n" " Don't close this page" msgstr "" +"等待 {} 确认, 你也可以复制链接发给他/她
\n" +" 不要关闭本页面" #: authentication/views/login.py:312 msgid "No ticket found" -msgstr "" +msgstr "没有发现工单" #: authentication/views/login.py:348 msgid "Logout success" -msgstr "" +msgstr "退出登录成功" #: authentication/views/login.py:349 msgid "Logout success, return login page" -msgstr "" +msgstr "退出登录成功,返回到登录页面" #: authentication/views/wecom.py:41 msgid "WeCom Error, Please contact your system administrator" -msgstr "" +msgstr "企业微信错误,请联系系统管理员" #: authentication/views/wecom.py:44 msgid "WeCom Error" -msgstr "" +msgstr "企业微信错误" #: authentication/views/wecom.py:163 msgid "WeCom query user failed" -msgstr "" +msgstr "企业微信查询用户失败" #: authentication/views/wecom.py:172 msgid "The WeCom is already bound to another user" -msgstr "" +msgstr "该企业微信已经绑定其他用户" #: authentication/views/wecom.py:179 msgid "Binding WeCom successfully" -msgstr "" +msgstr "绑定 企业微信 成功" #: authentication/views/wecom.py:231 authentication/views/wecom.py:285 msgid "Failed to get user from WeCom" -msgstr "" +msgstr "从企业微信获取用户失败" #: authentication/views/wecom.py:238 authentication/views/wecom.py:292 msgid "Please login with a password and then bind the WeCom" -msgstr "" +msgstr "请使用密码登录,然后绑定企业微信" #: common/const/__init__.py:6 #, python-format msgid "%(name)s was created successfully" -msgstr "" +msgstr "%(name)s 创建成功" #: common/const/__init__.py:7 #, python-format msgid "%(name)s was updated successfully" -msgstr "" +msgstr "%(name)s 更新成功" #: common/const/choices.py:10 msgid "Manual trigger" -msgstr "" +msgstr "手动触发" #: common/const/choices.py:11 msgid "Timing trigger" -msgstr "" +msgstr "定时触发" #: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 msgid "Ready" -msgstr "" +msgstr "准备" #: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 msgid "Pending" -msgstr "" +msgstr "待定的" #: common/const/choices.py:17 msgid "Running" msgstr "" #: common/const/choices.py:21 +#, fuzzy msgid "Canceled" -msgstr "" +msgstr "取消" #: common/db/encoder.py:11 msgid "ugettext_lazy" -msgstr "" +msgstr "ugettext_lazy" #: common/db/fields.py:94 msgid "Marshal dict data to char field" -msgstr "" +msgstr "编码 dict 为 char" #: common/db/fields.py:98 msgid "Marshal dict data to text field" -msgstr "" +msgstr "编码 dict 为 text" #: common/db/fields.py:110 msgid "Marshal list data to char field" -msgstr "" +msgstr "编码 list 为 char" #: common/db/fields.py:114 msgid "Marshal list data to text field" -msgstr "" +msgstr "编码 list 为 text" #: common/db/fields.py:118 msgid "Marshal data to char field" -msgstr "" +msgstr "编码数据为 char" #: common/db/fields.py:122 msgid "Marshal data to text field" -msgstr "" +msgstr "编码数据为 text" #: common/db/fields.py:164 msgid "Encrypt field using Secret Key" -msgstr "" +msgstr "加密的字段" #: common/db/models.py:76 msgid "Updated by" -msgstr "" +msgstr "更新人" #: common/drf/exc_handlers.py:25 msgid "Object" -msgstr "" +msgstr "对象" #: common/drf/fields.py:77 tickets/serializers/ticket/common.py:58 #: xpack/plugins/change_auth_plan/serializers/asset.py:64 @@ -2632,12 +2728,12 @@ msgstr "" #: xpack/plugins/change_auth_plan/serializers/asset.py:101 #: xpack/plugins/cloud/serializers/account_attrs.py:56 msgid "This field is required." -msgstr "" +msgstr "该字段是必填项。" #: common/drf/fields.py:78 -#, python-brace-format +#, fuzzy, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "" +msgstr "%s对象不存在" #: common/drf/fields.py:79 #, python-brace-format @@ -2649,146 +2745,149 @@ msgid "Invalid data type, should be list" msgstr "" #: common/drf/fields.py:156 +#, fuzzy msgid "Invalid choice: {}" -msgstr "" +msgstr "无效IP" #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" -msgstr "" +msgstr "文件内容太大 (最大长度 `{}` 字节)" #: common/drf/parsers/base.py:159 msgid "Parse file error: {}" -msgstr "" +msgstr "解析文件错误: {}" #: common/drf/serializers/common.py:86 msgid "Children" msgstr "" #: common/drf/serializers/common.py:94 +#, fuzzy msgid "File" -msgstr "" +msgstr "文件名" #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." -msgstr "" +msgstr "%s对象不存在" #: common/exceptions.py:25 msgid "Someone else is doing this. Please wait for complete" -msgstr "" +msgstr "其他人正在操作,请等待他人完成" #: common/exceptions.py:30 msgid "Your request timeout" -msgstr "" +msgstr "您的请求超时了" #: common/exceptions.py:35 msgid "M2M reverse not allowed" -msgstr "" +msgstr "多对多反向是不被允许的" #: common/exceptions.py:41 msgid "Is referenced by other objects and cannot be deleted" -msgstr "" +msgstr "被其他对象关联,不能删除" #: common/exceptions.py:48 msgid "This action require confirm current user" -msgstr "" +msgstr "此操作需要确认当前用户" #: common/exceptions.py:56 msgid "Unexpect error occur" -msgstr "" +msgstr "发生意外错误" #: common/mixins/api/action.py:52 msgid "Request file format may be wrong" -msgstr "" +msgstr "上传的文件格式错误 或 其它类型资源的文件" #: common/mixins/models.py:33 msgid "is discard" -msgstr "" +msgstr "忽略的" #: common/mixins/models.py:34 msgid "discard time" -msgstr "" +msgstr "忽略时间" #: common/mixins/views.py:58 msgid "Export all" -msgstr "" +msgstr "导出所有" #: common/mixins/views.py:60 msgid "Export only selected items" -msgstr "" +msgstr "仅导出选择项" #: common/mixins/views.py:65 #, python-format msgid "Export filtered: %s" -msgstr "" +msgstr "导出搜素: %s" #: common/plugins/es.py:28 msgid "Invalid elasticsearch config" -msgstr "" +msgstr "无效的 Elasticsearch 配置" #: common/plugins/es.py:33 msgid "Not Support Elasticsearch8" -msgstr "" +msgstr "不支持 Elasticsearch8" #: common/sdk/im/exceptions.py:23 msgid "Network error, please contact system administrator" -msgstr "" +msgstr "网络错误,请联系系统管理员" #: common/sdk/im/wecom/__init__.py:15 msgid "WeCom error, please contact system administrator" -msgstr "" +msgstr "企业微信错误,请联系系统管理员" #: common/sdk/sms/alibaba.py:56 msgid "Signature does not match" -msgstr "" +msgstr "签名不匹配" #: common/sdk/sms/cmpp2.py:46 msgid "sp_id is 6 bits" -msgstr "" +msgstr "SP_id 为6位" #: common/sdk/sms/cmpp2.py:216 msgid "Failed to connect to the CMPP gateway server, err: {}" -msgstr "" +msgstr "连接网关服务器错误,错误:{}" #: common/sdk/sms/endpoint.py:16 msgid "Alibaba cloud" -msgstr "" +msgstr "阿里云" #: common/sdk/sms/endpoint.py:17 msgid "Tencent cloud" -msgstr "" +msgstr "腾讯云" #: common/sdk/sms/endpoint.py:18 xpack/plugins/cloud/const.py:13 msgid "Huawei Cloud" -msgstr "" +msgstr "华为云" #: common/sdk/sms/endpoint.py:19 msgid "CMPP v2.0" -msgstr "" +msgstr "CMPP v2.0" #: common/sdk/sms/endpoint.py:30 msgid "SMS provider not support: {}" -msgstr "" +msgstr "短信服务商不支持:{}" #: common/sdk/sms/endpoint.py:51 msgid "SMS verification code signature or template invalid" -msgstr "" +msgstr "短信验证码签名或模版无效" #: common/sdk/sms/exceptions.py:8 msgid "The verification code has expired. Please resend it" -msgstr "" +msgstr "验证码已过期,请重新发送" #: common/sdk/sms/exceptions.py:13 msgid "The verification code is incorrect" -msgstr "" +msgstr "验证码错误" #: common/sdk/sms/exceptions.py:18 msgid "Please wait {} seconds before sending" -msgstr "" +msgstr "请在 {} 秒后发送" #: common/tasks.py:13 +#, fuzzy msgid "Send email" -msgstr "" +msgstr "发件人" #: common/tasks.py:40 msgid "Send email attachment" @@ -2796,43 +2895,44 @@ msgstr "" #: common/utils/ip/geoip/utils.py:26 msgid "Invalid ip" -msgstr "" +msgstr "无效IP" #: common/utils/ip/utils.py:78 +#, fuzzy msgid "Invalid address" -msgstr "" +msgstr "签名无效" #: common/validators.py:14 msgid "Special char not allowed" -msgstr "" +msgstr "不能包含特殊字符" #: common/validators.py:32 msgid "This field must be unique." -msgstr "" +msgstr "字段必须唯一" #: common/validators.py:40 msgid "Should not contains special characters" -msgstr "" +msgstr "不能包含特殊字符" #: common/validators.py:46 msgid "The mobile phone number format is incorrect" -msgstr "" +msgstr "手机号格式不正确" #: jumpserver/conf.py:413 msgid "Create account successfully" -msgstr "" +msgstr "创建账号成功" #: jumpserver/conf.py:415 msgid "Your account has been created successfully" -msgstr "" +msgstr "你的账号已创建成功" #: jumpserver/context_processor.py:12 msgid "JumpServer Open Source Bastion Host" -msgstr "" +msgstr "JumpServer 开源堡垒机" #: jumpserver/views/celery_flower.py:23 msgid "

Flower service unavailable, check it

" -msgstr "" +msgstr "Flower 服务不可用,请检查" #: jumpserver/views/other.py:26 msgid "" @@ -2840,10 +2940,12 @@ msgid "" "configure nginx for url distribution, If you see this page, " "prove that you are not accessing the nginx listening port. Good luck." msgstr "" +"
Luna是单独部署的一个程序,你需要部署luna,koko,
如果你看到了" +"这个页面,证明你访问的不是nginx监听的端口,祝你好运
" #: jumpserver/views/other.py:70 msgid "Websocket server run on port: {}, you should proxy it on nginx" -msgstr "" +msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置" #: jumpserver/views/other.py:84 msgid "" @@ -2851,42 +2953,47 @@ msgid "" "configure nginx for url distribution, If you see this page, " "prove that you are not accessing the nginx listening port. Good luck." msgstr "" +"
Koko是单独部署的一个程序,你需要部署Koko, 并确保nginx配置转发,
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运" #: notifications/apps.py:7 msgid "Notifications" -msgstr "" +msgstr "通知" #: notifications/backends/__init__.py:13 msgid "Site message" -msgstr "" +msgstr "站内信" #: notifications/models/notification.py:14 msgid "receive backend" -msgstr "" +msgstr "消息后端" #: notifications/models/notification.py:17 msgid "User message" -msgstr "" +msgstr "用户消息" #: notifications/models/notification.py:20 msgid "{} subscription" -msgstr "" +msgstr "{} 订阅" #: notifications/models/notification.py:32 msgid "System message" -msgstr "" +msgstr "系统信息" #: notifications/notifications.py:46 msgid "Publish the station message" msgstr "" #: ops/ansible/inventory.py:75 +#, fuzzy msgid "No account available" -msgstr "" +msgstr "账号无效" #: ops/ansible/inventory.py:178 +#, fuzzy msgid "Ansible disabled" -msgstr "" +msgstr "用户已禁用" #: ops/ansible/inventory.py:194 msgid "Skip hosts below:" @@ -2894,31 +3001,33 @@ msgstr "" #: ops/api/celery.py:63 ops/api/celery.py:78 msgid "Waiting task start" -msgstr "" +msgstr "等待任务开始" #: ops/apps.py:9 ops/notifications.py:16 msgid "App ops" -msgstr "" +msgstr "作业中心" #: ops/const.py:6 msgid "Push" msgstr "" #: ops/const.py:7 +#, fuzzy msgid "Verify" -msgstr "" +msgstr "已校验" #: ops/const.py:8 msgid "Collect" msgstr "" #: ops/const.py:9 +#, fuzzy msgid "Change password" -msgstr "" +msgstr "更改密码" #: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 msgid "Custom password" -msgstr "" +msgstr "自定义密码" #: ops/exception.py:6 msgid "no valid program entry found." @@ -2926,36 +3035,37 @@ msgstr "" #: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:73 msgid "Cycle perform" -msgstr "" +msgstr "周期执行" #: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 #: settings/serializers/auth/ldap.py:70 msgid "Regularly perform" -msgstr "" +msgstr "定期执行" #: ops/mixin.py:108 msgid "Interval" -msgstr "" +msgstr "间隔" #: ops/mixin.py:118 msgid "* Please enter a valid crontab expression" -msgstr "" +msgstr "* 请输入有效的 crontab 表达式" #: ops/mixin.py:125 msgid "Range {} to {}" -msgstr "" +msgstr "输入在 {} - {} 范围之间" #: ops/mixin.py:136 msgid "Require periodic or regularly perform setting" -msgstr "" +msgstr "需要周期或定期设置" #: ops/models/adhoc.py:18 ops/models/job.py:31 +#, fuzzy msgid "Powershell" -msgstr "" +msgstr "PowerShell" #: ops/models/adhoc.py:22 msgid "Pattern" -msgstr "" +msgstr "模式" #: ops/models/adhoc.py:24 ops/models/job.py:38 msgid "Module" @@ -2964,30 +3074,33 @@ msgstr "" #: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 #: terminal/models/component/task.py:17 msgid "Args" -msgstr "" +msgstr "参数" #: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 #: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 #: terminal/models/session/sharing.py:24 msgid "Creator" -msgstr "" +msgstr "创建者" #: ops/models/base.py:19 +#, fuzzy msgid "Account policy" -msgstr "" +msgstr "账号密钥" #: ops/models/base.py:20 +#, fuzzy msgid "Last execution" -msgstr "" +msgstr "命令执行" #: ops/models/base.py:22 +#, fuzzy msgid "Date last run" -msgstr "" +msgstr "最后同步日期" #: ops/models/base.py:51 ops/models/job.py:105 #: xpack/plugins/cloud/models.py:172 msgid "Result" -msgstr "" +msgstr "结果" #: ops/models/base.py:52 ops/models/job.py:106 msgid "Summary" @@ -2995,22 +3108,23 @@ msgstr "" #: ops/models/celery.py:55 terminal/models/component/task.py:18 msgid "Kwargs" -msgstr "" +msgstr "其它参数" #: ops/models/celery.py:56 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 #: tickets/serializers/ticket/ticket.py:21 msgid "State" -msgstr "" +msgstr "状态" #: ops/models/celery.py:57 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" -msgstr "" +msgstr "结束" #: ops/models/celery.py:58 +#, fuzzy msgid "Date published" -msgstr "" +msgstr "结束日期" #: ops/models/job.py:21 msgid "Adhoc" @@ -3062,53 +3176,57 @@ msgstr "" #: ops/notifications.py:17 msgid "Server performance" -msgstr "" +msgstr "监控告警" #: ops/notifications.py:23 msgid "Terminal health check warning" -msgstr "" +msgstr "终端健康状况检查警告" #: ops/notifications.py:68 #, python-brace-format msgid "The terminal is offline: {name}" -msgstr "" +msgstr "终端已离线: {name}" #: ops/notifications.py:73 #, python-brace-format msgid "Disk used more than {max_threshold}%: => {value}" -msgstr "" +msgstr "硬盘使用率超过 {max_threshold}%: => {value}" #: ops/notifications.py:78 #, python-brace-format msgid "Memory used more than {max_threshold}%: => {value}" -msgstr "" +msgstr "内存使用率超过 {max_threshold}%: => {value}" #: ops/notifications.py:83 #, python-brace-format msgid "CPU load more than {max_threshold}: => {value}" -msgstr "" +msgstr "CPU 使用率超过 {max_threshold}: => {value}" #: ops/serializers/job.py:10 +#, fuzzy msgid "Run after save" -msgstr "" +msgstr "运行的系统用户" #: ops/serializers/job.py:11 +#, fuzzy msgid "Job type" -msgstr "任务类型" +msgstr "文档类型" #: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 #: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" -msgstr "" +msgstr "任务" #: ops/tasks.py:28 +#, fuzzy msgid "Run ansible task" -msgstr "" +msgstr "运行的资产" #: ops/tasks.py:35 +#, fuzzy msgid "Run ansible task execution" -msgstr "" +msgstr "同步实例任务执行" #: ops/tasks.py:48 msgid "Periodic clear celery tasks" @@ -3116,64 +3234,67 @@ msgstr "" #: ops/tasks.py:50 msgid "Clean celery log period" -msgstr "" +msgstr "定期清除Celery日志" #: ops/tasks.py:67 +#, fuzzy msgid "Clear celery periodic tasks" -msgstr "" +msgstr "定期清除Celery日志" #: ops/tasks.py:90 msgid "Create or update periodic tasks" msgstr "" #: ops/tasks.py:98 +#, fuzzy msgid "Periodic check service performance" -msgstr "" +msgstr "定时执行" #: ops/templates/ops/celery_task_log.html:4 msgid "Task log" -msgstr "" +msgstr "任务列表" #: ops/utils.py:64 msgid "Update task content: {}" -msgstr "" +msgstr "更新任务内容: {}" #: orgs/api.py:67 msgid "The current organization ({}) cannot be deleted" -msgstr "" +msgstr "当前组织 ({}) 不能被删除" #: orgs/api.py:72 msgid "" "LDAP synchronization is set to the current organization. Please switch to " "another organization before deleting" -msgstr "" +msgstr "LDAP 同步设置组织为当前组织,请切换其他组织后再进行删除操作" #: orgs/api.py:81 msgid "The organization have resource ({}) cannot be deleted" -msgstr "" +msgstr "组织存在资源 ({}) 不能被删除" #: orgs/apps.py:7 rbac/tree.py:113 msgid "App organizations" -msgstr "" +msgstr "组织管理" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 #: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:62 msgid "Organization" -msgstr "" +msgstr "组织" #: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 msgid "Org name" -msgstr "" +msgstr "组织名称" #: orgs/models.py:72 +#, fuzzy msgid "Builtin" -msgstr "" +msgstr "内置" #: orgs/models.py:80 msgid "GLOBAL" -msgstr "" +msgstr "全局组织" #: orgs/models.py:82 msgid "DEFAULT" @@ -3185,27 +3306,29 @@ msgstr "" #: orgs/models.py:90 msgid "Can view root org" -msgstr "" +msgstr "可以查看全局组织" #: orgs/models.py:91 msgid "Can view all joined org" -msgstr "" +msgstr "可以查看所有加入的组织" #: orgs/tasks.py:9 +#, fuzzy msgid "Refresh organization cache" -msgstr "" +msgstr "全局组织名" #: perms/apps.py:9 msgid "App permissions" -msgstr "" +msgstr "授权管理" #: perms/const.py:12 msgid "Connect" -msgstr "" +msgstr "连接" #: perms/const.py:15 +#, fuzzy msgid "Copy" -msgstr "" +msgstr "复制链接" #: perms/const.py:16 msgid "Paste" @@ -3216,76 +3339,78 @@ msgid "Transfer" msgstr "" #: perms/const.py:27 +#, fuzzy msgid "Clipboard" -msgstr "" +msgstr "剪贴板复制" #: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 #: perms/serializers/permission.py:29 perms/serializers/permission.py:59 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:18 msgid "Actions" -msgstr "" +msgstr "动作" #: perms/models/asset_permission.py:73 msgid "From ticket" -msgstr "" +msgstr "来自工单" #: perms/models/asset_permission.py:81 msgid "Asset permission" -msgstr "" +msgstr "资产授权" #: perms/models/perm_node.py:55 msgid "Ungrouped" -msgstr "" +msgstr "未分组" #: perms/models/perm_node.py:57 msgid "Favorite" -msgstr "" +msgstr "收藏夹" #: perms/models/perm_node.py:104 msgid "Permed asset" -msgstr "" +msgstr "授权的资产" #: perms/models/perm_node.py:106 msgid "Can view my assets" -msgstr "" +msgstr "可以查看我的资产" #: perms/models/perm_node.py:107 msgid "Can view user assets" -msgstr "" +msgstr "可以查看用户授权的资产" #: perms/models/perm_node.py:108 msgid "Can view usergroup assets" -msgstr "" +msgstr "可以查看用户组授权的资产" #: perms/models/perm_node.py:119 +#, fuzzy msgid "Permed account" -msgstr "" +msgstr "收集账号" #: perms/notifications.py:12 perms/notifications.py:44 msgid "today" -msgstr "" +msgstr "今" #: perms/notifications.py:15 msgid "You permed assets is about to expire" -msgstr "" +msgstr "你授权的资产即将到期" #: perms/notifications.py:20 msgid "permed assets" -msgstr "" +msgstr "授权的资产" #: perms/notifications.py:59 msgid "Asset permissions is about to expire" -msgstr "" +msgstr "资产授权规则将要过期" #: perms/notifications.py:64 msgid "asset permissions of organization {}" -msgstr "" +msgstr "组织 ({}) 的资产授权" #: perms/serializers/permission.py:31 perms/serializers/permission.py:60 #: users/serializers/user.py:100 users/serializers/user.py:205 msgid "Is expired" -msgstr "" +msgstr "已过期" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 @@ -3295,667 +3420,674 @@ msgid "" " The following %(item_type)s will expire in %(count)s days\n" " " msgstr "" +"\n" +" 以下 %(item_type)s 即将在 %(count)s 天后过期\n" +" " #: perms/templates/perms/_msg_permed_items_expire.html:21 msgid "If you have any question, please contact the administrator" -msgstr "" +msgstr "如果有疑问或需求,请联系系统管理员" #: perms/utils/user_permission.py:627 rbac/tree.py:57 msgid "My assets" -msgstr "" +msgstr "我的资产" #: rbac/api/role.py:34 msgid "Internal role, can't be destroy" -msgstr "" +msgstr "内部角色,不能删除" #: rbac/api/role.py:38 msgid "The role has been bound to users, can't be destroy" -msgstr "" +msgstr "角色已绑定用户,不能删除" #: rbac/api/role.py:60 msgid "Internal role, can't be update" -msgstr "" +msgstr "内部角色,不能更新" #: rbac/api/rolebinding.py:52 msgid "{} at least one system role" -msgstr "" +msgstr "{} 至少有一个系统角色" #: rbac/apps.py:7 msgid "RBAC" -msgstr "" +msgstr "RBAC" #: rbac/builtin.py:111 msgid "SystemAdmin" -msgstr "" +msgstr "系统管理员" #: rbac/builtin.py:114 msgid "SystemAuditor" -msgstr "" +msgstr "系统审计员" #: rbac/builtin.py:117 msgid "SystemComponent" -msgstr "" +msgstr "系统组件" #: rbac/builtin.py:123 msgid "OrgAdmin" -msgstr "" +msgstr "组织管理员" #: rbac/builtin.py:126 msgid "OrgAuditor" -msgstr "" +msgstr "组织审计员" #: rbac/builtin.py:129 msgid "OrgUser" -msgstr "" +msgstr "组织用户" #: rbac/models/menu.py:13 msgid "Menu permission" -msgstr "" +msgstr "菜单授权" #: rbac/models/menu.py:15 msgid "Can view console view" -msgstr "" +msgstr "可以显示控制台" #: rbac/models/menu.py:16 msgid "Can view audit view" -msgstr "" +msgstr "可以显示审计台" #: rbac/models/menu.py:17 msgid "Can view workbench view" -msgstr "" +msgstr "可以显示工作台" #: rbac/models/menu.py:18 msgid "Can view web terminal" -msgstr "" +msgstr "Web终端" #: rbac/models/menu.py:19 msgid "Can view file manager" -msgstr "" +msgstr "文件管理" #: rbac/models/permission.py:26 rbac/models/role.py:34 msgid "Permissions" -msgstr "" +msgstr "授权" #: rbac/models/role.py:31 rbac/models/rolebinding.py:38 #: settings/serializers/auth/oauth2.py:37 msgid "Scope" -msgstr "" +msgstr "范围" #: rbac/models/role.py:36 msgid "Built-in" -msgstr "" +msgstr "内置" #: rbac/models/role.py:46 rbac/models/rolebinding.py:44 #: users/models/user.py:685 msgid "Role" -msgstr "" +msgstr "角色" #: rbac/models/role.py:144 msgid "System role" -msgstr "" +msgstr "系统角色" #: rbac/models/role.py:152 msgid "Organization role" -msgstr "" +msgstr "组织角色" #: rbac/models/rolebinding.py:53 msgid "Role binding" -msgstr "" +msgstr "角色绑定" #: rbac/models/rolebinding.py:137 msgid "All organizations" -msgstr "" +msgstr "所有组织" #: rbac/models/rolebinding.py:166 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" -msgstr "" +msgstr "用户最后一个角色,不能删除,你可以将用户从组织移除" #: rbac/models/rolebinding.py:173 msgid "Organization role binding" -msgstr "" +msgstr "组织角色绑定" #: rbac/models/rolebinding.py:188 msgid "System role binding" -msgstr "" +msgstr "系统角色绑定" #: rbac/serializers/permission.py:26 users/serializers/profile.py:132 msgid "Perms" -msgstr "" +msgstr "权限" #: rbac/serializers/role.py:11 msgid "Scope display" -msgstr "" +msgstr "范围名称" #: rbac/serializers/role.py:26 users/serializers/group.py:34 msgid "Users amount" -msgstr "" +msgstr "用户数量" #: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 msgid "Display name" -msgstr "" +msgstr "显示名称" #: rbac/serializers/rolebinding.py:22 msgid "Role display" -msgstr "" +msgstr "角色显示" #: rbac/serializers/rolebinding.py:56 msgid "Has bound this role" -msgstr "" +msgstr "已经绑定" #: rbac/tree.py:18 rbac/tree.py:19 msgid "All permissions" -msgstr "" +msgstr "所有权限" #: rbac/tree.py:25 msgid "Console view" -msgstr "" +msgstr "控制台" #: rbac/tree.py:26 msgid "Workbench view" -msgstr "" +msgstr "工作台" #: rbac/tree.py:27 msgid "Audit view" -msgstr "" +msgstr "审计台" #: rbac/tree.py:28 settings/models.py:156 msgid "System setting" -msgstr "" +msgstr "系统设置" #: rbac/tree.py:29 msgid "Other" -msgstr "" +msgstr "其它" #: rbac/tree.py:41 msgid "Session audits" -msgstr "" +msgstr "会话审计" #: rbac/tree.py:51 msgid "Cloud import" -msgstr "" +msgstr "云同步" #: rbac/tree.py:52 msgid "Backup account" -msgstr "" +msgstr "备份账号" #: rbac/tree.py:53 msgid "Gather account" -msgstr "" +msgstr "收集账号" #: rbac/tree.py:54 msgid "App change auth" -msgstr "" +msgstr "应用改密" #: rbac/tree.py:55 msgid "Asset change auth" -msgstr "" +msgstr "资产改密" #: rbac/tree.py:56 msgid "Terminal setting" -msgstr "" +msgstr "终端设置" #: rbac/tree.py:58 msgid "My apps" -msgstr "" +msgstr "我的应用" #: rbac/tree.py:114 msgid "Ticket comment" -msgstr "" +msgstr "工单评论" #: rbac/tree.py:115 tickets/models/ticket/general.py:306 msgid "Ticket" -msgstr "" +msgstr "工单管理" #: rbac/tree.py:116 msgid "Common setting" -msgstr "" +msgstr "一般设置" #: rbac/tree.py:117 msgid "View permission tree" -msgstr "" +msgstr "查看授权树" #: rbac/tree.py:118 msgid "Execute batch command" -msgstr "" +msgstr "执行批量命令" #: settings/api/dingtalk.py:31 settings/api/feishu.py:36 #: settings/api/sms.py:148 settings/api/wecom.py:37 msgid "Test success" -msgstr "" +msgstr "测试成功" #: settings/api/email.py:20 msgid "Test mail sent to {}, please check" -msgstr "" +msgstr "邮件已经发送{}, 请检查" #: settings/api/ldap.py:166 msgid "Synchronization start, please wait." -msgstr "" +msgstr "同步开始,请稍等" #: settings/api/ldap.py:170 msgid "Synchronization is running, please wait." -msgstr "" +msgstr "同步正在运行,请稍等" #: settings/api/ldap.py:175 msgid "Synchronization error: {}" -msgstr "" +msgstr "同步错误: {}" #: settings/api/ldap.py:213 msgid "Get ldap users is None" -msgstr "" +msgstr "获取 LDAP 用户为 None" #: settings/api/ldap.py:222 msgid "Imported {} users successfully (Organization: {})" -msgstr "" +msgstr "成功导入 {} 个用户 ( 组织: {} )" #: settings/api/sms.py:130 msgid "Invalid SMS platform" -msgstr "" +msgstr "无效的短信平台" #: settings/api/sms.py:136 msgid "test_phone is required" -msgstr "" +msgstr "测试手机号 该字段是必填项。" #: settings/apps.py:7 msgid "Settings" -msgstr "" +msgstr "系统设置" #: settings/models.py:36 msgid "Encrypted" -msgstr "" +msgstr "加密的" #: settings/models.py:158 msgid "Can change email setting" -msgstr "" +msgstr "邮件设置" #: settings/models.py:159 msgid "Can change auth setting" -msgstr "" +msgstr "认证设置" #: settings/models.py:160 msgid "Can change system msg sub setting" -msgstr "" +msgstr "消息订阅设置" #: settings/models.py:161 msgid "Can change sms setting" -msgstr "" +msgstr "短信设置" #: settings/models.py:162 msgid "Can change security setting" -msgstr "" +msgstr "安全设置" #: settings/models.py:163 msgid "Can change clean setting" -msgstr "" +msgstr "定期清理" #: settings/models.py:164 msgid "Can change interface setting" -msgstr "" +msgstr "界面设置" #: settings/models.py:165 msgid "Can change license setting" -msgstr "" +msgstr "许可证设置" #: settings/models.py:166 msgid "Can change terminal setting" -msgstr "" +msgstr "终端设置" #: settings/models.py:167 msgid "Can change other setting" -msgstr "" +msgstr "其它设置" #: settings/serializers/auth/base.py:12 msgid "CAS Auth" -msgstr "" +msgstr "CAS 认证" #: settings/serializers/auth/base.py:13 msgid "OPENID Auth" -msgstr "" +msgstr "OIDC 认证" #: settings/serializers/auth/base.py:14 msgid "RADIUS Auth" -msgstr "" +msgstr "RADIUS 认证" #: settings/serializers/auth/base.py:15 msgid "DingTalk Auth" -msgstr "" +msgstr "钉钉 认证" #: settings/serializers/auth/base.py:16 msgid "FeiShu Auth" -msgstr "" +msgstr "飞书 认证" #: settings/serializers/auth/base.py:17 msgid "WeCom Auth" -msgstr "" +msgstr "企业微信 认证" #: settings/serializers/auth/base.py:18 msgid "SSO Auth" -msgstr "" +msgstr "SSO Token 认证" #: settings/serializers/auth/base.py:19 msgid "SAML2 Auth" -msgstr "" +msgstr "SAML2 认证" #: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 msgid "Forgot password url" -msgstr "" +msgstr "忘记密码 URL" #: settings/serializers/auth/base.py:28 msgid "Enable login redirect msg" -msgstr "" +msgstr "启用登录跳转提示" #: settings/serializers/auth/cas.py:10 msgid "CAS" -msgstr "" +msgstr "CAS" #: settings/serializers/auth/cas.py:12 msgid "Enable CAS Auth" -msgstr "" +msgstr "启用 CAS 认证" #: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 msgid "Server url" -msgstr "" +msgstr "服务端地址" #: settings/serializers/auth/cas.py:16 msgid "Proxy server url" -msgstr "" +msgstr "回调地址" #: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:55 #: settings/serializers/auth/saml2.py:34 msgid "Logout completely" -msgstr "" +msgstr "同步注销" #: settings/serializers/auth/cas.py:23 msgid "Username attr" -msgstr "" +msgstr "用户名属性" #: settings/serializers/auth/cas.py:26 msgid "Enable attributes map" -msgstr "" +msgstr "启用属性映射" #: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 msgid "Rename attr" -msgstr "" +msgstr "映射属性" #: settings/serializers/auth/cas.py:29 msgid "Create user if not" -msgstr "" +msgstr "创建用户(如果不存在)" #: settings/serializers/auth/dingtalk.py:15 msgid "Enable DingTalk Auth" -msgstr "" +msgstr "启用钉钉认证" #: settings/serializers/auth/feishu.py:14 msgid "Enable FeiShu Auth" -msgstr "" +msgstr "启用飞书认证" #: settings/serializers/auth/ldap.py:39 msgid "LDAP" -msgstr "" +msgstr "LDAP" #: settings/serializers/auth/ldap.py:42 msgid "LDAP server" -msgstr "" +msgstr "LDAP 地址" #: settings/serializers/auth/ldap.py:43 msgid "eg: ldap://localhost:389" -msgstr "" +msgstr "如: ldap://localhost:389" #: settings/serializers/auth/ldap.py:45 msgid "Bind DN" -msgstr "" +msgstr "绑定 DN" #: settings/serializers/auth/ldap.py:50 msgid "User OU" -msgstr "" +msgstr "用户 OU" #: settings/serializers/auth/ldap.py:51 msgid "Use | split multi OUs" -msgstr "" +msgstr "多个 OU 使用 | 分割" #: settings/serializers/auth/ldap.py:54 msgid "User search filter" -msgstr "" +msgstr "用户过滤器" #: settings/serializers/auth/ldap.py:55 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" -msgstr "" +msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" #: settings/serializers/auth/ldap.py:58 settings/serializers/auth/oauth2.py:57 #: settings/serializers/auth/oidc.py:37 msgid "User attr map" -msgstr "" +msgstr "用户属性映射" #: settings/serializers/auth/ldap.py:59 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" msgstr "" +"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," +"email 是jumpserver的用户需要属性" #: settings/serializers/auth/ldap.py:77 msgid "Connect timeout" -msgstr "" +msgstr "连接超时时间" #: settings/serializers/auth/ldap.py:79 msgid "Search paged size" -msgstr "" +msgstr "搜索分页数量" #: settings/serializers/auth/ldap.py:81 msgid "Enable LDAP auth" -msgstr "" +msgstr "启用 LDAP 认证" #: settings/serializers/auth/oauth2.py:19 msgid "OAuth2" -msgstr "" +msgstr "OAuth2" #: settings/serializers/auth/oauth2.py:22 msgid "Enable OAuth2 Auth" -msgstr "" +msgstr "启用 OAuth2 认证" #: settings/serializers/auth/oauth2.py:25 msgid "Logo" -msgstr "" +msgstr "图标" #: settings/serializers/auth/oauth2.py:28 msgid "Service provider" -msgstr "" +msgstr "服务提供商" #: settings/serializers/auth/oauth2.py:31 settings/serializers/auth/oidc.py:19 msgid "Client Id" -msgstr "" +msgstr "客户端 ID" #: settings/serializers/auth/oauth2.py:34 settings/serializers/auth/oidc.py:22 #: xpack/plugins/cloud/serializers/account_attrs.py:38 msgid "Client Secret" -msgstr "" +msgstr "客户端密钥" #: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 msgid "Provider auth endpoint" -msgstr "" +msgstr "授权端点地址" #: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 msgid "Provider token endpoint" -msgstr "" +msgstr "token 端点地址" #: settings/serializers/auth/oauth2.py:46 settings/serializers/auth/oidc.py:30 msgid "Client authentication method" -msgstr "" +msgstr "客户端认证方式" #: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 msgid "Provider userinfo endpoint" -msgstr "" +msgstr "用户信息端点地址" #: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 msgid "Provider end session endpoint" -msgstr "" +msgstr "注销会话端点地址" #: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 #: settings/serializers/auth/saml2.py:35 msgid "Always update user" -msgstr "" +msgstr "总是更新用户信息" #: settings/serializers/auth/oidc.py:12 msgid "OIDC" -msgstr "" +msgstr "OIDC" #: settings/serializers/auth/oidc.py:16 msgid "Base site url" -msgstr "" +msgstr "JumpServer 地址" #: settings/serializers/auth/oidc.py:32 msgid "Share session" -msgstr "" +msgstr "共享会话" #: settings/serializers/auth/oidc.py:34 msgid "Ignore ssl verification" -msgstr "" +msgstr "忽略 SSL 证书验证" #: settings/serializers/auth/oidc.py:38 msgid "" "User attr map present how to map OpenID user attr to jumpserver, username," "name,email is jumpserver attr" msgstr "" +"用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上,username, name," +"email 是jumpserver的用户需要属性" #: settings/serializers/auth/oidc.py:46 msgid "Use Keycloak" -msgstr "" +msgstr "使用 Keycloak" #: settings/serializers/auth/oidc.py:52 msgid "Realm name" -msgstr "" +msgstr "域" #: settings/serializers/auth/oidc.py:58 msgid "Enable OPENID Auth" -msgstr "" +msgstr "启用 OIDC 认证" #: settings/serializers/auth/oidc.py:60 msgid "Provider endpoint" -msgstr "" +msgstr "端点地址" #: settings/serializers/auth/oidc.py:69 msgid "Provider jwks endpoint" -msgstr "" +msgstr "jwks 端点地址" #: settings/serializers/auth/oidc.py:78 msgid "Provider sign alg" -msgstr "" +msgstr "签名算法" #: settings/serializers/auth/oidc.py:81 msgid "Provider sign key" -msgstr "" +msgstr "签名 Key" #: settings/serializers/auth/oidc.py:83 msgid "Scopes" -msgstr "" +msgstr "连接范围" #: settings/serializers/auth/oidc.py:85 msgid "Id token max age" -msgstr "" +msgstr "令牌有效时间" #: settings/serializers/auth/oidc.py:88 msgid "Id token include claims" -msgstr "" +msgstr "声明" #: settings/serializers/auth/oidc.py:90 msgid "Use state" -msgstr "" +msgstr "使用状态" #: settings/serializers/auth/oidc.py:91 msgid "Use nonce" -msgstr "" +msgstr "临时使用" #: settings/serializers/auth/radius.py:13 msgid "Radius" -msgstr "" +msgstr "Radius" #: settings/serializers/auth/radius.py:15 msgid "Enable Radius Auth" -msgstr "" +msgstr "启用 Radius 认证" #: settings/serializers/auth/radius.py:21 msgid "OTP in Radius" -msgstr "" +msgstr "使用 Radius OTP" #: settings/serializers/auth/saml2.py:11 msgid "SAML2" -msgstr "" +msgstr "SAML2" #: settings/serializers/auth/saml2.py:14 msgid "Enable SAML2 Auth" -msgstr "" +msgstr "启用 SAML2 认证" #: settings/serializers/auth/saml2.py:17 msgid "IDP metadata URL" -msgstr "" +msgstr "IDP metadata 地址" #: settings/serializers/auth/saml2.py:20 msgid "IDP metadata XML" -msgstr "" +msgstr "IDP metadata XML" #: settings/serializers/auth/saml2.py:23 msgid "SP advanced settings" -msgstr "" +msgstr "高级设置" #: settings/serializers/auth/saml2.py:27 msgid "SP private key" -msgstr "" +msgstr "SP 密钥" #: settings/serializers/auth/saml2.py:31 msgid "SP cert" -msgstr "" +msgstr "SP 证书" #: settings/serializers/auth/sms.py:15 msgid "Enable SMS" -msgstr "" +msgstr "启用 SMS" #: settings/serializers/auth/sms.py:17 msgid "SMS provider / Protocol" -msgstr "" +msgstr "短信服务商 / 协议" #: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:45 #: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 #: settings/serializers/auth/sms.py:73 settings/serializers/email.py:68 msgid "Signature" -msgstr "" +msgstr "签名" #: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:46 #: settings/serializers/auth/sms.py:54 settings/serializers/auth/sms.py:63 msgid "Template code" -msgstr "" +msgstr "模板" #: settings/serializers/auth/sms.py:31 msgid "Test phone" -msgstr "" +msgstr "测试手机号" #: settings/serializers/auth/sms.py:60 msgid "App Access Address" -msgstr "" +msgstr "应用地址" #: settings/serializers/auth/sms.py:61 msgid "Signature channel number" -msgstr "" +msgstr "签名通道号" #: settings/serializers/auth/sms.py:69 msgid "Enterprise code(SP id)" -msgstr "" +msgstr "企业代码(SP id)" #: settings/serializers/auth/sms.py:70 msgid "Shared secret(Shared secret)" -msgstr "" +msgstr "共享密码(Shared secret)" #: settings/serializers/auth/sms.py:71 msgid "Original number(Src id)" -msgstr "" +msgstr "原始号码(Src id)" #: settings/serializers/auth/sms.py:72 msgid "Business type(Service id)" -msgstr "" +msgstr "业务类型(Service id)" #: settings/serializers/auth/sms.py:75 msgid "Template" -msgstr "" +msgstr "模板" #: settings/serializers/auth/sms.py:76 #, python-brace-format @@ -3964,200 +4096,206 @@ msgid "" "67 words. For example, your verification code is {code}, which is valid for " "5 minutes. Please do not disclose it to others." msgstr "" +"模板需要包含 {code},并且模板+签名长度不能超过67个字。例如, 您的验证码是 " +"{code}, 有效期为5分钟。请不要泄露给其他人。" #: settings/serializers/auth/sms.py:85 #, python-brace-format msgid "The template needs to contain {code}" -msgstr "" +msgstr "模板需要包含 {code}" #: settings/serializers/auth/sms.py:88 msgid "Signature + Template must not exceed 65 words" -msgstr "" +msgstr "模板+签名不能超过65个字" #: settings/serializers/auth/sso.py:13 msgid "Enable SSO auth" -msgstr "" +msgstr "启用 SSO Token 认证" #: settings/serializers/auth/sso.py:14 msgid "Other service can using SSO token login to JumpServer without password" -msgstr "" +msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过程" #: settings/serializers/auth/sso.py:17 msgid "SSO auth key TTL" -msgstr "" +msgstr "Token 有效期" #: settings/serializers/auth/sso.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "Unit: second" -msgstr "" +msgstr "单位: 秒" #: settings/serializers/auth/wecom.py:15 msgid "Enable WeCom Auth" -msgstr "" +msgstr "启用企业微信认证" #: settings/serializers/basic.py:9 msgid "Subject" -msgstr "" +msgstr "主题" #: settings/serializers/basic.py:13 msgid "More url" -msgstr "" +msgstr "更多信息 URL" #: settings/serializers/basic.py:30 msgid "Site url" -msgstr "" +msgstr "当前站点URL" #: settings/serializers/basic.py:31 msgid "eg: http://dev.jumpserver.org:8080" -msgstr "" +msgstr "如: http://dev.jumpserver.org:8080" #: settings/serializers/basic.py:34 msgid "User guide url" -msgstr "" +msgstr "用户向导URL" #: settings/serializers/basic.py:35 msgid "User first login update profile done redirect to it" -msgstr "" +msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档" #: settings/serializers/basic.py:39 msgid "" "The forgot password url on login page, If you use ldap or cas external " "authentication, you can set it" msgstr "" +"登录页面忘记密码URL, 如果使用了 LDAP, OPENID 等外部认证系统,可以自定义用户重" +"置密码访问的地址" #: settings/serializers/basic.py:43 msgid "Global organization name" -msgstr "" +msgstr "全局组织名" #: settings/serializers/basic.py:44 msgid "The name of global organization to display" -msgstr "" +msgstr "全局组织的显示名称,默认为 全局组织" #: settings/serializers/basic.py:46 msgid "Enable announcement" -msgstr "" +msgstr "启用公告" #: settings/serializers/basic.py:47 msgid "Announcement" -msgstr "" +msgstr "公告" #: settings/serializers/basic.py:48 msgid "Enable tickets" -msgstr "" +msgstr "启用工单" #: settings/serializers/cleaning.py:8 msgid "Period clean" -msgstr "" +msgstr "定時清掃" #: settings/serializers/cleaning.py:12 msgid "Login log keep days" -msgstr "" +msgstr "登录日志" #: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 #: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 #: settings/serializers/cleaning.py:28 msgid "Unit: day" -msgstr "" +msgstr "单位: 天" #: settings/serializers/cleaning.py:16 msgid "Task log keep days" -msgstr "" +msgstr "任务日志" #: settings/serializers/cleaning.py:20 msgid "Operate log keep days" -msgstr "" +msgstr "操作日志" #: settings/serializers/cleaning.py:24 msgid "FTP log keep days" -msgstr "" +msgstr "上传下载" #: settings/serializers/cleaning.py:28 msgid "Cloud sync record keep days" -msgstr "" +msgstr "云同步记录" #: settings/serializers/cleaning.py:31 msgid "Session keep duration" -msgstr "" +msgstr "会话日志保存时间" #: settings/serializers/cleaning.py:32 msgid "" "Unit: days, Session, record, command will be delete if more than duration, " "only in database" msgstr "" +"单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" +"受影响)" #: settings/serializers/email.py:21 msgid "SMTP host" -msgstr "" +msgstr "SMTP 主机" #: settings/serializers/email.py:22 msgid "SMTP port" -msgstr "" +msgstr "SMTP 端口" #: settings/serializers/email.py:23 msgid "SMTP account" -msgstr "" +msgstr "SMTP 账号" #: settings/serializers/email.py:25 msgid "SMTP password" -msgstr "" +msgstr "SMTP 密码" #: settings/serializers/email.py:26 msgid "Tips: Some provider use token except password" -msgstr "" +msgstr "提示:一些邮件提供商需要输入的是授权码" #: settings/serializers/email.py:29 msgid "Send user" -msgstr "" +msgstr "发件人" #: settings/serializers/email.py:30 msgid "Tips: Send mail account, default SMTP account as the send account" -msgstr "" +msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" #: settings/serializers/email.py:33 msgid "Test recipient" -msgstr "" +msgstr "测试收件人" #: settings/serializers/email.py:34 msgid "Tips: Used only as a test mail recipient" -msgstr "" +msgstr "提示:仅用来作为测试邮件收件人" #: settings/serializers/email.py:38 msgid "If SMTP port is 465, may be select" -msgstr "" +msgstr "如果SMTP端口是465,通常需要启用 SSL" #: settings/serializers/email.py:41 msgid "Use TLS" -msgstr "" +msgstr "使用 TLS" #: settings/serializers/email.py:42 msgid "If SMTP port is 587, may be select" -msgstr "" +msgstr "如果SMTP端口是587,通常需要启用 TLS" #: settings/serializers/email.py:45 msgid "Subject prefix" -msgstr "" +msgstr "主题前缀" #: settings/serializers/email.py:54 msgid "Create user email subject" -msgstr "" +msgstr "邮件主题" #: settings/serializers/email.py:55 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" -msgstr "" +msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" #: settings/serializers/email.py:59 msgid "Create user honorific" -msgstr "" +msgstr "邮件问候语" #: settings/serializers/email.py:60 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" -msgstr "" +msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 你好)" #: settings/serializers/email.py:64 msgid "Create user email content" -msgstr "" +msgstr "邮件的内容" #: settings/serializers/email.py:65 #, python-brace-format @@ -4165,165 +4303,168 @@ msgid "" "Tips: When creating a user, send the content of the email, support " "{username} {name} {email} label" msgstr "" +"提示: 创建用户时,发送设置密码邮件的内容, 支持 {username} {name} {email} 标签" #: settings/serializers/email.py:69 msgid "Tips: Email signature (eg:jumpserver)" -msgstr "" +msgstr "邮件署名 (如:jumpserver)" #: settings/serializers/other.py:6 msgid "More..." -msgstr "" +msgstr "更多..." #: settings/serializers/other.py:9 msgid "Email suffix" -msgstr "" +msgstr "邮件后缀" #: settings/serializers/other.py:10 msgid "" "This is used by default if no email is returned during SSO authentication" -msgstr "" +msgstr "SSO认证时,如果没有返回邮件地址,将使用该后缀" #: settings/serializers/other.py:14 msgid "OTP issuer name" -msgstr "" +msgstr "OTP 扫描后的名称" #: settings/serializers/other.py:18 msgid "OTP valid window" -msgstr "" +msgstr "OTP 延迟有效次数" #: settings/serializers/other.py:23 msgid "CMD" -msgstr "" +msgstr "CMD" #: settings/serializers/other.py:24 msgid "PowerShell" -msgstr "" +msgstr "PowerShell" #: settings/serializers/other.py:26 msgid "Shell (Windows)" -msgstr "" +msgstr "Windows shell" #: settings/serializers/other.py:27 msgid "The shell type used when Windows assets perform ansible tasks" -msgstr "" +msgstr "windows 资产执行 Ansible 任务时,使用的 Shell 类型。" #: settings/serializers/other.py:31 msgid "Perm ungroup node" -msgstr "" +msgstr "显示未分组节点" #: settings/serializers/other.py:32 msgid "Perm single to ungroup node" msgstr "" +"放置单独授权的资产到未分组节点, 避免能看到资产所在节点,但该节点未被授权的问" +"题" #: settings/serializers/other.py:37 msgid "Ticket authorize default time" -msgstr "" +msgstr "默认工单授权时间" #: settings/serializers/other.py:40 msgid "day" -msgstr "" +msgstr "天" #: settings/serializers/other.py:40 msgid "hour" -msgstr "" +msgstr "时" #: settings/serializers/other.py:41 msgid "Ticket authorize default time unit" -msgstr "" +msgstr "默认工单授权时间单位" #: settings/serializers/other.py:44 msgid "Help Docs URL" -msgstr "" +msgstr "文档链接" #: settings/serializers/other.py:45 msgid "default: http://docs.jumpserver.org" -msgstr "" +msgstr "默认: http://dev.jumpserver.org:8080" #: settings/serializers/other.py:49 msgid "Help Support URL" -msgstr "" +msgstr "支持链接" #: settings/serializers/other.py:50 msgid "default: http://www.jumpserver.org/support/" -msgstr "" +msgstr "默认: http://www.jumpserver.org/support/" #: settings/serializers/security.py:10 msgid "Password minimum length" -msgstr "" +msgstr "密码最小长度" #: settings/serializers/security.py:14 msgid "Admin user password minimum length" -msgstr "" +msgstr "管理员密码最小长度" #: settings/serializers/security.py:17 msgid "Must contain capital" -msgstr "" +msgstr "必须包含大写字符" #: settings/serializers/security.py:20 msgid "Must contain lowercase" -msgstr "" +msgstr "必须包含小写字符" #: settings/serializers/security.py:23 msgid "Must contain numeric" -msgstr "" +msgstr "必须包含数字" #: settings/serializers/security.py:26 msgid "Must contain special" -msgstr "" +msgstr "必须包含特殊字符" #: settings/serializers/security.py:31 msgid "" "Unit: minute, If the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." -msgstr "" +msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" #: settings/serializers/security.py:40 msgid "All users" -msgstr "" +msgstr "所有用户" #: settings/serializers/security.py:41 msgid "Only admin users" -msgstr "" +msgstr "仅管理员" #: settings/serializers/security.py:43 msgid "Global MFA auth" -msgstr "" +msgstr "全局启用 MFA 认证" #: settings/serializers/security.py:47 msgid "Third-party login users perform MFA authentication" -msgstr "" +msgstr "第三方登录用户进行MFA认证" #: settings/serializers/security.py:48 msgid "The third-party login modes include OIDC, CAS, and SAML2" -msgstr "" +msgstr "第三方登录方式包括: OIDC、CAS、SAML2" #: settings/serializers/security.py:52 msgid "Limit the number of user login failures" -msgstr "" +msgstr "限制用户登录失败次数" #: settings/serializers/security.py:56 msgid "Block user login interval" -msgstr "" +msgstr "禁止用户登录时间间隔" #: settings/serializers/security.py:61 msgid "Limit the number of IP login failures" -msgstr "" +msgstr "限制 IP 登录失败次数" #: settings/serializers/security.py:65 msgid "Block IP login interval" -msgstr "" +msgstr "禁止 IP 登录时间间隔" #: settings/serializers/security.py:69 msgid "Login IP White List" -msgstr "" +msgstr "IP 登录白名单" #: settings/serializers/security.py:74 msgid "Login IP Black List" -msgstr "" +msgstr "IP 登录黑名单" #: settings/serializers/security.py:80 msgid "User password expiration" -msgstr "" +msgstr "用户密码过期时间" #: settings/serializers/security.py:82 msgid "" @@ -4332,146 +4473,150 @@ msgid "" "be automatic sent to the user by system within 5 days (daily) before the " "password expires" msgstr "" +"单位:天, 如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件" +"将在密码过期前5天内由系统(每天)自动发送给用户" #: settings/serializers/security.py:89 msgid "Number of repeated historical passwords" -msgstr "" +msgstr "不能设置近几次密码" #: settings/serializers/security.py:91 msgid "" "Tip: When the user resets the password, it cannot be the previous n " "historical passwords of the user" -msgstr "" +msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码" #: settings/serializers/security.py:96 msgid "Only single device login" -msgstr "" +msgstr "仅一台设备登录" #: settings/serializers/security.py:97 msgid "Next device login, pre login will be logout" -msgstr "" +msgstr "下个设备登录,上次登录会被顶掉" #: settings/serializers/security.py:100 msgid "Only exist user login" -msgstr "" +msgstr "仅已存在用户登录" #: settings/serializers/security.py:101 msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" -msgstr "" +msgstr "开启后,如果系统中不存在该用户,CAS、OIDC 登录将会失败" #: settings/serializers/security.py:104 msgid "Only from source login" -msgstr "" +msgstr "仅从用户来源登录" #: settings/serializers/security.py:105 msgid "Only log in from the user source property" -msgstr "" +msgstr "开启后,如果用户来源为本地,CAS、OIDC 登录将会失败" #: settings/serializers/security.py:109 msgid "MFA verify TTL" -msgstr "" +msgstr "MFA 校验有效期" #: settings/serializers/security.py:111 msgid "" "Unit: second, The verification MFA takes effect only when you view the " "account password" -msgstr "" +msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效" #: settings/serializers/security.py:116 msgid "Enable Login dynamic code" -msgstr "" +msgstr "启用登录附加码" #: settings/serializers/security.py:117 msgid "" "The password and additional code are sent to a third party authentication " "system for verification" msgstr "" +"密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密" +"码+6位数字 完成认证" #: settings/serializers/security.py:122 msgid "MFA in login page" -msgstr "" +msgstr "MFA 在登录页面输入" #: settings/serializers/security.py:123 msgid "Eu security regulations(GDPR) require MFA to be on the login page" -msgstr "" +msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全" #: settings/serializers/security.py:126 msgid "Enable Login captcha" -msgstr "" +msgstr "启用登录验证码" #: settings/serializers/security.py:127 msgid "Enable captcha to prevent robot authentication" -msgstr "" +msgstr "开启验证码,防止机器人登录" #: settings/serializers/security.py:146 msgid "Security" -msgstr "" +msgstr "安全" #: settings/serializers/security.py:149 msgid "Enable terminal register" -msgstr "" +msgstr "终端注册" #: settings/serializers/security.py:151 msgid "" "Allow terminal register, after all terminal setup, you should disable this " "for security" -msgstr "" +msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" #: settings/serializers/security.py:155 msgid "Enable watermark" -msgstr "" +msgstr "开启水印" #: settings/serializers/security.py:156 msgid "Enabled, the web session and replay contains watermark information" -msgstr "" +msgstr "启用后,Web 会话和录像将包含水印信息" #: settings/serializers/security.py:160 msgid "Connection max idle time" -msgstr "" +msgstr "连接最大空闲时间" #: settings/serializers/security.py:161 msgid "If idle time more than it, disconnect connection Unit: minute" -msgstr "" +msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" #: settings/serializers/security.py:164 msgid "Remember manual auth" -msgstr "" +msgstr "保存手动输入密码" #: settings/serializers/security.py:167 msgid "Enable change auth secure mode" -msgstr "" +msgstr "启用改密安全模式" #: settings/serializers/security.py:170 msgid "Insecure command alert" -msgstr "" +msgstr "危险命令告警" #: settings/serializers/security.py:173 msgid "Email recipient" -msgstr "" +msgstr "邮件收件人" #: settings/serializers/security.py:174 msgid "Multiple user using , split" -msgstr "" +msgstr "多个用户,使用 , 分割" #: settings/serializers/security.py:177 msgid "Batch command execution" -msgstr "" +msgstr "批量命令执行" #: settings/serializers/security.py:178 msgid "Allow user run batch command or not using ansible" -msgstr "" +msgstr "是否允许用户使用 ansible 执行批量命令" #: settings/serializers/security.py:181 msgid "Session share" -msgstr "" +msgstr "会话分享" #: settings/serializers/security.py:182 msgid "Enabled, Allows user active session to be shared with other users" -msgstr "" +msgstr "开启后允许用户分享已连接的资产会话给他人,协同工作" #: settings/serializers/security.py:185 msgid "Remote Login Protection" -msgstr "" +msgstr "异地登录保护" #: settings/serializers/security.py:187 msgid "" @@ -4479,224 +4624,229 @@ msgid "" "city. If the account is logged in from a common login city, the system sends " "a remote login reminder" msgstr "" +"根据登录 IP 是否所属常用登录城市进行判断,若账号在非常用城市登录,会发送异地" +"登录提醒" #: settings/serializers/terminal.py:15 msgid "Auto" -msgstr "" +msgstr "自动" #: settings/serializers/terminal.py:21 msgid "Password auth" -msgstr "" +msgstr "密码认证" #: settings/serializers/terminal.py:23 msgid "Public key auth" -msgstr "" +msgstr "密钥认证" #: settings/serializers/terminal.py:24 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" msgstr "" +"提示:如果你使用其它认证方式,如 AD/LDAP,你应该禁用此项,以避免第三方系统删" +"除后,还可以登录" #: settings/serializers/terminal.py:28 msgid "List sort by" -msgstr "" +msgstr "资产列表排序" #: settings/serializers/terminal.py:31 msgid "List page size" -msgstr "" +msgstr "资产列表每页数量" #: settings/serializers/terminal.py:34 msgid "Telnet login regex" -msgstr "" +msgstr "Telnet 成功正则表达式" #: settings/serializers/terminal.py:35 msgid "" "Tips: The login success message varies with devices. if you cannot log in to " "the device through Telnet, set this parameter" msgstr "" +"提示: 不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" #: settings/serializers/terminal.py:38 msgid "Enable database proxy" -msgstr "" +msgstr "启用数据库组件" #: settings/serializers/terminal.py:39 msgid "Enable Razor" -msgstr "" +msgstr "启用 Razor 服务" #: settings/serializers/terminal.py:40 msgid "Enable SSH Client" -msgstr "" +msgstr "启用 SSH Client" #: settings/serializers/terminal.py:51 msgid "Default graphics resolution" -msgstr "" +msgstr "默认图形化分辨率" #: settings/serializers/terminal.py:52 msgid "" "Tip: Default resolution to use when connecting graphical assets in Luna pages" -msgstr "" +msgstr "提示:在Luna 页面中连接图形化资产时默认使用的分辨率" #: settings/utils/ldap.py:467 msgid "ldap:// or ldaps:// protocol is used." -msgstr "" +msgstr "使用 ldap:// 或 ldaps:// 协议" #: settings/utils/ldap.py:478 msgid "Host or port is disconnected: {}" -msgstr "" +msgstr "主机或端口不可连接: {}" #: settings/utils/ldap.py:480 msgid "The port is not the port of the LDAP service: {}" -msgstr "" +msgstr "端口不是LDAP服务端口: {}" #: settings/utils/ldap.py:482 msgid "Please add certificate: {}" -msgstr "" +msgstr "请添加证书" #: settings/utils/ldap.py:486 settings/utils/ldap.py:513 #: settings/utils/ldap.py:543 settings/utils/ldap.py:571 msgid "Unknown error: {}" -msgstr "" +msgstr "未知错误: {}" #: settings/utils/ldap.py:500 msgid "Bind DN or Password incorrect" -msgstr "" +msgstr "绑定DN或密码错误" #: settings/utils/ldap.py:507 msgid "Please enter Bind DN: {}" -msgstr "" +msgstr "请输入绑定DN: {}" #: settings/utils/ldap.py:509 msgid "Please enter Password: {}" -msgstr "" +msgstr "请输入密码: {}" #: settings/utils/ldap.py:511 msgid "Please enter correct Bind DN and Password: {}" -msgstr "" +msgstr "请输入正确的绑定DN和密码: {}" #: settings/utils/ldap.py:529 msgid "Invalid User OU or User search filter: {}" -msgstr "" +msgstr "不合法的用户OU或用户过滤器: {}" #: settings/utils/ldap.py:560 msgid "LDAP User attr map not include: {}" -msgstr "" +msgstr "LDAP属性映射没有包含: {}" #: settings/utils/ldap.py:567 msgid "LDAP User attr map is not dict" -msgstr "" +msgstr "LDAP属性映射不合法" #: settings/utils/ldap.py:586 msgid "LDAP authentication is not enabled" -msgstr "" +msgstr "LDAP认证没有启用" #: settings/utils/ldap.py:604 msgid "Error (Invalid LDAP server): {}" -msgstr "" +msgstr "错误 (不合法的LDAP服务器地址): {}" #: settings/utils/ldap.py:606 msgid "Error (Invalid Bind DN): {}" -msgstr "" +msgstr "错误(不合法的绑定DN): {}" #: settings/utils/ldap.py:608 msgid "Error (Invalid LDAP User attr map): {}" -msgstr "" +msgstr "错误(不合法的LDAP属性映射): {}" #: settings/utils/ldap.py:610 msgid "Error (Invalid User OU or User search filter): {}" -msgstr "" +msgstr "错误(不合法的用户OU或用户过滤器): {}" #: settings/utils/ldap.py:612 msgid "Error (Not enabled LDAP authentication): {}" -msgstr "" +msgstr "错误(没有启用LDAP认证): {}" #: settings/utils/ldap.py:614 msgid "Error (Unknown): {}" -msgstr "" +msgstr "错误(未知): {}" #: settings/utils/ldap.py:617 msgid "Succeed: Match {} s user" -msgstr "" +msgstr "成功匹配 {} 个用户" #: settings/utils/ldap.py:650 msgid "Authentication failed (configuration incorrect): {}" -msgstr "" +msgstr "认证失败(配置错误): {}" #: settings/utils/ldap.py:654 msgid "Authentication failed (username or password incorrect): {}" -msgstr "" +msgstr "认证失败 (用户名或密码不正确): {}" #: settings/utils/ldap.py:656 msgid "Authentication failed (Unknown): {}" -msgstr "" +msgstr "认证失败: (未知): {}" #: settings/utils/ldap.py:659 msgid "Authentication success: {}" -msgstr "" +msgstr "认证成功: {}" #: templates/_csv_import_export.html:8 msgid "Export" -msgstr "" +msgstr "导出" #: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 msgid "Import" -msgstr "" +msgstr "导入" #: templates/_csv_import_modal.html:12 msgid "Download the imported template or use the exported CSV file format" -msgstr "" +msgstr "下载导入的模板或使用导出的csv格式" #: templates/_csv_import_modal.html:13 msgid "Download the import template" -msgstr "" +msgstr "下载导入模版" #: templates/_csv_import_modal.html:17 templates/_csv_update_modal.html:17 msgid "Select the CSV file to import" -msgstr "" +msgstr "请选择csv文件导入" #: templates/_csv_import_modal.html:39 templates/_csv_update_modal.html:42 msgid "Please select file" -msgstr "" +msgstr "选择文件" #: templates/_csv_update_modal.html:12 msgid "Download the update template or use the exported CSV file format" -msgstr "" +msgstr "下载更新的模板或使用导出的csv格式" #: templates/_csv_update_modal.html:13 msgid "Download the update template" -msgstr "" +msgstr "下载更新模版" #: templates/_header_bar.html:12 msgid "Help" -msgstr "" +msgstr "帮助" #: templates/_header_bar.html:19 msgid "Docs" -msgstr "" +msgstr "文档" #: templates/_header_bar.html:25 msgid "Commercial support" -msgstr "" +msgstr "商业支持" #: templates/_header_bar.html:76 users/forms/profile.py:44 msgid "Profile" -msgstr "" +msgstr "个人信息" #: templates/_header_bar.html:79 msgid "Admin page" -msgstr "" +msgstr "管理页面" #: templates/_header_bar.html:81 msgid "User page" -msgstr "" +msgstr "用户页面" #: templates/_header_bar.html:84 msgid "API Key" -msgstr "" +msgstr "API Key" #: templates/_header_bar.html:85 msgid "Logout" -msgstr "" +msgstr "注销登录" #: templates/_message.html:6 msgid "" @@ -4704,14 +4854,16 @@ msgid "" " Your account has expired, please contact the administrator.\n" " " msgstr "" +"\n" +" 您的账号已经过期,请联系管理员。 " #: templates/_message.html:13 msgid "Your account will at" -msgstr "" +msgstr "您的账号将于" #: templates/_message.html:13 templates/_message.html:30 msgid "expired. " -msgstr "" +msgstr "过期。" #: templates/_message.html:23 #, python-format @@ -4721,10 +4873,14 @@ msgid "" "href=\"%(user_password_update_url)s\"> this link update password.\n" " " msgstr "" +"\n" +" 您的密码已经过期,请点击 链接 更新密码\n" +" " #: templates/_message.html:30 msgid "Your password will at" -msgstr "" +msgstr "您的密码将于" #: templates/_message.html:31 #, python-format @@ -4734,6 +4890,10 @@ msgid "" "link to update your password.\n" " " msgstr "" +"\n" +" 请点击 链接 更" +"新密码\n" +" " #: templates/_message.html:43 #, python-format @@ -4743,6 +4903,10 @@ msgid "" "href=\"%(first_login_url)s\"> this link to complete your information.\n" " " msgstr "" +"\n" +" 你的信息不完整,请点击 链接 " +" 补充完整\n" +" " #: templates/_message.html:56 #, python-format @@ -4752,68 +4916,74 @@ msgid "" "href=\"%(user_pubkey_update)s\"> this link to update\n" " " msgstr "" +"\n" +" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" +" " #: templates/_mfa_login_field.html:28 msgid "Send verification code" -msgstr "" +msgstr "发送验证码" #: templates/_mfa_login_field.html:106 #: users/templates/users/forgot_password.html:129 msgid "Wait: " -msgstr "" +msgstr "等待:" #: templates/_mfa_login_field.html:116 #: users/templates/users/forgot_password.html:145 msgid "The verification code has been sent" -msgstr "" +msgstr "验证码已发送" #: templates/_without_nav_base.html:26 msgid "Home page" -msgstr "" +msgstr "首页" #: templates/resource_download.html:18 templates/resource_download.html:31 msgid "Client" -msgstr "" +msgstr "客户端" #: templates/resource_download.html:20 msgid "" "JumpServer Client, currently used to launch the client, now only support " "launch RDP SSH client, The Telnet client will next" msgstr "" +"JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP SSH " +"客户端,Telnet 会在未来支持" #: templates/resource_download.html:31 msgid "Microsoft" -msgstr "" +msgstr "微软" #: templates/resource_download.html:31 msgid "Official" -msgstr "" +msgstr "官方" #: templates/resource_download.html:33 msgid "" "macOS needs to download the client to connect RDP asset, which comes with " "Windows" -msgstr "" +msgstr "macOS 需要下载客户端来连接 RDP 资产,Windows 系统默认安装了该程序" #: templates/resource_download.html:42 msgid "Windows Remote application publisher tools" -msgstr "" +msgstr "Windows 远程应用发布服务器工具" #: templates/resource_download.html:43 msgid "" "OpenSSH is a program used to connect remote applications in the Windows " "Remote Application Publisher" -msgstr "" +msgstr "OpenSSH 是在 windows 远程应用发布服务器中用来连接远程应用的程序" #: templates/resource_download.html:48 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" -msgstr "" +msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远程应用的程序" #: templates/resource_download.html:57 msgid "Offline video player" -msgstr "" +msgstr "离线录像播放器" #: terminal/api/component/endpoint.py:31 msgid "Not found protocol query params" @@ -4821,138 +4991,141 @@ msgstr "" #: terminal/api/component/storage.py:28 msgid "Deleting the default storage is not allowed" -msgstr "" +msgstr "不允许删除默认存储配置" #: terminal/api/component/storage.py:31 msgid "Cannot delete storage that is being used" -msgstr "" +msgstr "不允许删除正在使用的存储配置" #: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 msgid "Command storages" -msgstr "" +msgstr "命令存储" #: terminal/api/component/storage.py:79 msgid "Invalid" -msgstr "" +msgstr "无效" #: terminal/api/component/storage.py:119 msgid "Test failure: {}" -msgstr "" +msgstr "测试失败: {}" #: terminal/api/component/storage.py:122 msgid "Test successful" -msgstr "" +msgstr "测试成功" #: terminal/api/component/storage.py:124 msgid "Test failure: Account invalid" -msgstr "" +msgstr "测试失败: 账号无效" #: terminal/api/component/terminal.py:38 msgid "Have online sessions" -msgstr "" +msgstr "有在线会话" #: terminal/api/session/session.py:217 msgid "Session does not exist: {}" -msgstr "" +msgstr "会话不存在: {}" #: terminal/api/session/session.py:220 msgid "Session is finished or the protocol not supported" -msgstr "" +msgstr "会话已经完成或协议不支持" #: terminal/api/session/session.py:233 msgid "User does not have permission" -msgstr "" +msgstr "用户没有权限" #: terminal/api/session/sharing.py:29 msgid "Secure session sharing settings is disabled" -msgstr "" +msgstr "未开启会话共享" #: terminal/apps.py:9 msgid "Terminals" -msgstr "" +msgstr "终端管理" #: terminal/backends/command/models.py:16 msgid "Ordinary" -msgstr "" +msgstr "普通" #: terminal/backends/command/models.py:17 msgid "Dangerous" -msgstr "" +msgstr "危险" #: terminal/backends/command/models.py:23 msgid "Input" -msgstr "" +msgstr "输入" #: terminal/backends/command/models.py:24 #: terminal/backends/command/serializers.py:38 msgid "Output" -msgstr "" +msgstr "输出" #: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 #: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 #: terminal/templates/terminal/_msg_command_alert.html:10 #: tickets/models/ticket/command_confirm.py:17 msgid "Session" -msgstr "" +msgstr "会话" #: terminal/backends/command/models.py:26 #: terminal/backends/command/serializers.py:18 msgid "Risk level" -msgstr "" +msgstr "风险等级" #: terminal/backends/command/serializers.py:16 msgid "Session ID" -msgstr "" +msgstr "会话ID" #: terminal/backends/command/serializers.py:37 +#, fuzzy msgid "Account " -msgstr "" +msgstr "账号" #: terminal/backends/command/serializers.py:39 msgid "Risk level display" -msgstr "" +msgstr "风险等级名称" #: terminal/backends/command/serializers.py:40 msgid "Timestamp" -msgstr "" +msgstr "时间戳" #: terminal/backends/command/serializers.py:42 #: terminal/models/component/terminal.py:85 msgid "Remote Address" -msgstr "" +msgstr "远端地址" #: terminal/const.py:37 msgid "Critical" -msgstr "" +msgstr "严重" #: terminal/const.py:38 msgid "High" -msgstr "" +msgstr "较高" #: terminal/const.py:39 users/templates/users/reset_password.html:50 msgid "Normal" -msgstr "" +msgstr "正常" #: terminal/const.py:40 msgid "Offline" -msgstr "" +msgstr "离线" #: terminal/const.py:81 terminal/const.py:82 terminal/const.py:83 #: terminal/const.py:84 terminal/const.py:85 +#, fuzzy msgid "DB Client" -msgstr "" +msgstr "客户端" #: terminal/exceptions.py:8 msgid "Bulk create not support" -msgstr "" +msgstr "不支持批量创建" #: terminal/exceptions.py:13 msgid "Storage is invalid" -msgstr "" +msgstr "存储无效" #: terminal/models/applet/applet.py:23 +#, fuzzy msgid "Author" -msgstr "" +msgstr "资产账号" #: terminal/models/applet/applet.py:27 msgid "Tags" @@ -4960,31 +5133,36 @@ msgstr "" #: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 msgid "Hosts" -msgstr "" +msgstr "主机" #: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 +#, fuzzy msgid "Applet" -msgstr "" +msgstr "申请资产" #: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 +#, fuzzy msgid "Deploy options" -msgstr "" +msgstr "其他方式登录" #: terminal/models/applet/host.py:19 msgid "Inited" msgstr "" #: terminal/models/applet/host.py:20 +#, fuzzy msgid "Date inited" -msgstr "" +msgstr "结束日期" #: terminal/models/applet/host.py:21 +#, fuzzy msgid "Date synced" -msgstr "" +msgstr "同步日期" #: terminal/models/applet/host.py:102 +#, fuzzy msgid "Hosting" -msgstr "" +msgstr "主机" #: terminal/models/applet/host.py:103 msgid "Initial" @@ -4992,19 +5170,19 @@ msgstr "" #: terminal/models/component/endpoint.py:15 msgid "HTTPS Port" -msgstr "" +msgstr "HTTPS 端口" #: terminal/models/component/endpoint.py:16 msgid "HTTP Port" -msgstr "" +msgstr "HTTP 端口" #: terminal/models/component/endpoint.py:17 msgid "SSH Port" -msgstr "" +msgstr "SSH 端口" #: terminal/models/component/endpoint.py:18 msgid "RDP Port" -msgstr "" +msgstr "RDP 端口" #: terminal/models/component/endpoint.py:25 #: terminal/models/component/endpoint.py:94 terminal/serializers/endpoint.py:57 @@ -5012,205 +5190,210 @@ msgstr "" #: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 #: terminal/serializers/storage.py:98 msgid "Endpoint" -msgstr "" +msgstr "端点" #: terminal/models/component/endpoint.py:87 msgid "IP group" -msgstr "" +msgstr "IP 组" #: terminal/models/component/endpoint.py:99 msgid "Endpoint rule" -msgstr "" +msgstr "端点规则" #: terminal/models/component/status.py:14 msgid "Session Online" -msgstr "" +msgstr "在线会话" #: terminal/models/component/status.py:15 msgid "CPU Load" -msgstr "" +msgstr "CPU负载" #: terminal/models/component/status.py:16 msgid "Memory Used" -msgstr "" +msgstr "内存使用" #: terminal/models/component/status.py:17 msgid "Disk Used" -msgstr "" +msgstr "磁盘使用" #: terminal/models/component/status.py:18 msgid "Connections" -msgstr "" +msgstr "连接数" #: terminal/models/component/status.py:19 msgid "Threads" -msgstr "" +msgstr "线程数" #: terminal/models/component/status.py:20 msgid "Boot Time" -msgstr "" +msgstr "运行时间" #: terminal/models/component/storage.py:27 msgid "Default storage" -msgstr "" +msgstr "默认存储" #: terminal/models/component/storage.py:140 #: terminal/models/component/terminal.py:86 msgid "Command storage" -msgstr "" +msgstr "命令存储" #: terminal/models/component/storage.py:200 #: terminal/models/component/terminal.py:87 msgid "Replay storage" -msgstr "" +msgstr "录像存储" #: terminal/models/component/terminal.py:83 msgid "type" -msgstr "" +msgstr "类型" #: terminal/models/component/terminal.py:88 msgid "Application User" -msgstr "" +msgstr "应用用户" #: terminal/models/component/terminal.py:161 msgid "Can view terminal config" -msgstr "" +msgstr "可以查看终端配置" #: terminal/models/session/command.py:66 msgid "Command record" -msgstr "" +msgstr "命令记录" #: terminal/models/session/replay.py:12 msgid "Session replay" -msgstr "" +msgstr "会话录像" #: terminal/models/session/replay.py:14 msgid "Can upload session replay" -msgstr "" +msgstr "可以上传会话录像" #: terminal/models/session/replay.py:15 msgid "Can download session replay" -msgstr "" +msgstr "可以下载会话录像" #: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 msgid "Login from" -msgstr "" +msgstr "登录来源" #: terminal/models/session/session.py:40 msgid "Replay" -msgstr "" +msgstr "回放" #: terminal/models/session/session.py:44 msgid "Date end" -msgstr "" +msgstr "结束日期" #: terminal/models/session/session.py:236 msgid "Session record" -msgstr "" +msgstr "会话记录" #: terminal/models/session/session.py:238 msgid "Can monitor session" -msgstr "" +msgstr "可以监控会话" #: terminal/models/session/session.py:239 msgid "Can share session" -msgstr "" +msgstr "可以分享会话" #: terminal/models/session/session.py:240 msgid "Can terminate session" -msgstr "" +msgstr "可以终断会话" #: terminal/models/session/session.py:241 msgid "Can validate session action perm" -msgstr "" +msgstr "可以验证会话动作权限" #: terminal/models/session/sharing.py:31 msgid "Expired time (min)" -msgstr "" +msgstr "过期时间 (分)" #: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 msgid "Session sharing" -msgstr "" +msgstr "会话分享" #: terminal/models/session/sharing.py:39 msgid "Can add super session sharing" -msgstr "" +msgstr "可以创建超级会话分享" #: terminal/models/session/sharing.py:66 msgid "Link not active" -msgstr "" +msgstr "链接失效" #: terminal/models/session/sharing.py:68 msgid "Link expired" -msgstr "" +msgstr "链接过期" #: terminal/models/session/sharing.py:70 msgid "User not allowed to join" -msgstr "" +msgstr "该用户无权加入会话" #: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 msgid "Joiner" -msgstr "" +msgstr "加入者" #: terminal/models/session/sharing.py:90 msgid "Date joined" -msgstr "" +msgstr "加入日期" #: terminal/models/session/sharing.py:93 msgid "Date left" -msgstr "" +msgstr "结束日期" #: terminal/models/session/sharing.py:116 msgid "Session join record" -msgstr "" +msgstr "会话加入记录" #: terminal/models/session/sharing.py:132 msgid "Invalid verification code" -msgstr "" +msgstr "验证码不正确" #: terminal/notifications.py:22 msgid "Sessions" -msgstr "" +msgstr "会话管理" #: terminal/notifications.py:68 msgid "Danger command alert" -msgstr "" +msgstr "危险命令告警" #: terminal/notifications.py:95 terminal/notifications.py:143 msgid "Level" -msgstr "" +msgstr "级别" #: terminal/notifications.py:113 msgid "Batch danger command alert" -msgstr "" +msgstr "批量危险命令告警" #: terminal/serializers/applet.py:16 +#, fuzzy msgid "Published" -msgstr "" +msgstr "SSH公钥" #: terminal/serializers/applet.py:17 +#, fuzzy msgid "Unpublished" -msgstr "" +msgstr "结束" #: terminal/serializers/applet.py:18 +#, fuzzy msgid "Not match" -msgstr "" +msgstr "没有匹配到用户" #: terminal/serializers/applet.py:32 msgid "Icon" msgstr "" #: terminal/serializers/applet_host.py:21 +#, fuzzy msgid "Per Session" -msgstr "" +msgstr "会话" #: terminal/serializers/applet_host.py:22 msgid "Per Device" msgstr "" #: terminal/serializers/applet_host.py:28 +#, fuzzy msgid "RDS Licensing" -msgstr "" +msgstr "许可证" #: terminal/serializers/applet_host.py:29 msgid "RDS License Server" @@ -5234,140 +5417,140 @@ msgstr "" #: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 msgid "Load status" -msgstr "" +msgstr "负载状态" #: terminal/serializers/endpoint.py:14 msgid "Magnus listen db port" -msgstr "" +msgstr "Magnus 监听的数据库端口" #: terminal/serializers/endpoint.py:17 msgid "Magnus Listen port range" -msgstr "" +msgstr "Magnus 监听的端口范围" #: terminal/serializers/endpoint.py:19 msgid "" "The range of ports that Magnus listens on is modified in the configuration " "file" -msgstr "" +msgstr "请在配置文件中修改 Magnus 监听的端口范围" #: terminal/serializers/endpoint.py:51 msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" -msgstr "" +msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" #: terminal/serializers/session.py:17 terminal/serializers/session.py:42 msgid "Terminal display" -msgstr "" +msgstr "终端显示" #: terminal/serializers/session.py:33 msgid "User ID" -msgstr "" +msgstr "用户 ID" #: terminal/serializers/session.py:34 msgid "Asset ID" -msgstr "" +msgstr "资产 ID" #: terminal/serializers/session.py:35 msgid "Login from display" -msgstr "" +msgstr "登录来源名称" #: terminal/serializers/session.py:37 msgid "Can replay" -msgstr "" +msgstr "是否可重放" #: terminal/serializers/session.py:38 msgid "Can join" -msgstr "" +msgstr "是否可加入" #: terminal/serializers/session.py:39 msgid "Terminal ID" -msgstr "" +msgstr "终端 ID" #: terminal/serializers/session.py:40 msgid "Is finished" -msgstr "" +msgstr "是否完成" #: terminal/serializers/session.py:41 msgid "Can terminate" -msgstr "" +msgstr "是否可中断" #: terminal/serializers/session.py:47 msgid "Command amount" -msgstr "" +msgstr "命令数量" #: terminal/serializers/storage.py:20 msgid "Endpoint invalid: remove path `{}`" -msgstr "" +msgstr "端点无效: 移除路径 `{}`" #: terminal/serializers/storage.py:26 msgid "Bucket" -msgstr "" +msgstr "桶名称" #: terminal/serializers/storage.py:30 #: xpack/plugins/cloud/serializers/account_attrs.py:17 msgid "Access key id" -msgstr "" +msgstr "访问密钥 ID(AK)" #: terminal/serializers/storage.py:34 #: xpack/plugins/cloud/serializers/account_attrs.py:20 msgid "Access key secret" -msgstr "" +msgstr "访问密钥密文(SK)" #: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:219 msgid "Region" -msgstr "" +msgstr "地域" #: terminal/serializers/storage.py:109 msgid "Container name" -msgstr "" +msgstr "容器名称" #: terminal/serializers/storage.py:112 msgid "Account key" -msgstr "" +msgstr "账号密钥" #: terminal/serializers/storage.py:115 msgid "Endpoint suffix" -msgstr "" +msgstr "端点后缀" #: terminal/serializers/storage.py:135 msgid "The address format is incorrect" -msgstr "" +msgstr "地址格式不正确" #: terminal/serializers/storage.py:142 msgid "Host invalid" -msgstr "" +msgstr "主机无效" #: terminal/serializers/storage.py:145 msgid "Port invalid" -msgstr "" +msgstr "端口无效" #: terminal/serializers/storage.py:160 msgid "Index by date" -msgstr "" +msgstr "按日期建索引" #: terminal/serializers/storage.py:161 msgid "Whether to create an index by date" -msgstr "" +msgstr "是否根据日期动态建立索引" #: terminal/serializers/storage.py:164 msgid "Index" -msgstr "" +msgstr "索引" #: terminal/serializers/storage.py:166 msgid "Doc type" -msgstr "" +msgstr "文档类型" #: terminal/serializers/storage.py:168 msgid "Ignore Certificate Verification" -msgstr "" +msgstr "忽略证书认证" #: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 msgid "Not found" -msgstr "" +msgstr "没有发现" #: terminal/templates/terminal/_msg_command_alert.html:10 msgid "view" -msgstr "" +msgstr "查看" #: terminal/utils/db_port_mapper.py:64 msgid "" @@ -5375,28 +5558,30 @@ msgid "" "number of ports open to the database agent service, Contact the " "administrator to open more ports." msgstr "" +"未匹配到可用端口,数据库的数量可能已经超过数据库代理服务开放的端口数量,请联" +"系管理员开放更多端口。" #: terminal/utils/db_port_mapper.py:90 msgid "" "No ports can be used, check and modify the limit on the number of ports that " "Magnus listens on in the configuration file." -msgstr "" +msgstr "没有端口可以使用,检查并修改配置文件中 Magnus 监听的端口数量限制。" #: terminal/utils/db_port_mapper.py:92 msgid "All available port count: {}, Already use port count: {}" -msgstr "" +msgstr "所有可用端口数量:{},已使用端口数量:{}" #: tickets/apps.py:7 msgid "Tickets" -msgstr "" +msgstr "工单管理" #: tickets/const.py:9 msgid "Apply for asset" -msgstr "" +msgstr "申请资产" #: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 msgid "Open" -msgstr "" +msgstr "打开" #: tickets/const.py:18 tickets/const.py:31 msgid "Reopen" @@ -5404,69 +5589,70 @@ msgstr "" #: tickets/const.py:19 tickets/const.py:32 msgid "Approved" -msgstr "" +msgstr "已同意" #: tickets/const.py:20 tickets/const.py:33 msgid "Rejected" -msgstr "" +msgstr "已拒绝" #: tickets/const.py:30 tickets/const.py:38 msgid "Closed" -msgstr "" +msgstr "关闭的" #: tickets/const.py:46 msgid "Approve" -msgstr "" +msgstr "同意" #: tickets/const.py:50 msgid "One level" -msgstr "" +msgstr "1 级" #: tickets/const.py:51 msgid "Two level" -msgstr "" +msgstr "2 级" #: tickets/const.py:55 msgid "Org admin" -msgstr "" +msgstr "组织管理员" #: tickets/const.py:56 msgid "Custom user" -msgstr "" +msgstr "自定义用户" #: tickets/const.py:57 msgid "Super admin" -msgstr "" +msgstr "超级管理员" #: tickets/const.py:58 msgid "Super admin and org admin" -msgstr "" +msgstr "组织管理员或超级管理员" #: tickets/errors.py:9 msgid "Ticket already closed" -msgstr "" +msgstr "工单已经关闭" #: tickets/handlers/apply_asset.py:36 msgid "" "Created by the ticket ticket title: {} ticket applicant: {} ticket " "processor: {} ticket ID: {}" msgstr "" +"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" #: tickets/handlers/base.py:84 msgid "Change field" -msgstr "" +msgstr "变更字段" #: tickets/handlers/base.py:84 msgid "Before change" -msgstr "" +msgstr "变更前" #: tickets/handlers/base.py:84 msgid "After change" -msgstr "" +msgstr "变更后" #: tickets/handlers/base.py:96 msgid "{} {} the ticket" -msgstr "" +msgstr "{} {} 工单" #: tickets/models/comment.py:14 msgid "common" @@ -5474,291 +5660,295 @@ msgstr "" #: tickets/models/comment.py:23 msgid "User display name" -msgstr "" +msgstr "用户显示名称" #: tickets/models/comment.py:24 msgid "Body" -msgstr "" +msgstr "内容" #: tickets/models/flow.py:20 tickets/models/flow.py:62 #: tickets/models/ticket/general.py:39 msgid "Approve level" -msgstr "" +msgstr "审批级别" #: tickets/models/flow.py:25 tickets/serializers/flow.py:18 msgid "Approve strategy" -msgstr "" +msgstr "审批策略" #: tickets/models/flow.py:30 tickets/serializers/flow.py:20 msgid "Assignees" -msgstr "" +msgstr "受理人" #: tickets/models/flow.py:34 msgid "Ticket flow approval rule" -msgstr "" +msgstr "工单批准信息" #: tickets/models/flow.py:67 msgid "Ticket flow" -msgstr "" +msgstr "工单流程" #: tickets/models/relation.py:10 msgid "Ticket session relation" -msgstr "" +msgstr "工单会话" #: tickets/models/ticket/apply_application.py:10 #: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" -msgstr "" +msgstr "授权规则名称" #: tickets/models/ticket/apply_application.py:19 msgid "Apply applications" -msgstr "" +msgstr "申请应用" #: tickets/models/ticket/apply_application.py:22 msgid "Apply system users" -msgstr "" +msgstr "申请的系统用户" #: tickets/models/ticket/apply_asset.py:9 #: tickets/serializers/ticket/apply_asset.py:14 msgid "Select at least one asset or node" -msgstr "" +msgstr "资产或者节点至少选择一项" #: tickets/models/ticket/apply_asset.py:14 #: tickets/serializers/ticket/apply_asset.py:19 msgid "Apply nodes" -msgstr "" +msgstr "申请节点" #: tickets/models/ticket/apply_asset.py:16 #: tickets/serializers/ticket/apply_asset.py:18 msgid "Apply assets" -msgstr "" +msgstr "申请资产" #: tickets/models/ticket/apply_asset.py:17 +#, fuzzy msgid "Apply accounts" -msgstr "" +msgstr "应用账号" #: tickets/models/ticket/command_confirm.py:10 msgid "Run user" -msgstr "" +msgstr "运行的用户" #: tickets/models/ticket/command_confirm.py:12 msgid "Run asset" -msgstr "" +msgstr "运行的资产" #: tickets/models/ticket/command_confirm.py:13 msgid "Run command" -msgstr "" +msgstr "运行的命令" #: tickets/models/ticket/command_confirm.py:14 +#, fuzzy msgid "Run account" -msgstr "" +msgstr "账号" #: tickets/models/ticket/command_confirm.py:21 msgid "From cmd filter" -msgstr "" +msgstr "来自命令过滤规则" #: tickets/models/ticket/command_confirm.py:25 msgid "From cmd filter rule" -msgstr "" +msgstr "来自命令过滤规则" #: tickets/models/ticket/general.py:74 msgid "Ticket step" -msgstr "" +msgstr "工单步骤" #: tickets/models/ticket/general.py:92 msgid "Ticket assignee" -msgstr "" +msgstr "工单受理人" #: tickets/models/ticket/general.py:271 msgid "Title" -msgstr "" +msgstr "标题" #: tickets/models/ticket/general.py:287 msgid "Applicant" -msgstr "" +msgstr "申请人" #: tickets/models/ticket/general.py:291 msgid "TicketFlow" -msgstr "" +msgstr "工单流程" #: tickets/models/ticket/general.py:294 msgid "Approval step" -msgstr "" +msgstr "审批步骤" #: tickets/models/ticket/general.py:297 msgid "Relation snapshot" -msgstr "" +msgstr "工单快照" #: tickets/models/ticket/general.py:391 msgid "Please try again" -msgstr "" +msgstr "请再次尝试" #: tickets/models/ticket/general.py:424 msgid "Super ticket" -msgstr "" +msgstr "超级工单" #: tickets/models/ticket/login_asset_confirm.py:11 msgid "Login user" -msgstr "" +msgstr "登录用户" #: tickets/models/ticket/login_asset_confirm.py:14 msgid "Login asset" -msgstr "" +msgstr "登录资产" #: tickets/models/ticket/login_asset_confirm.py:17 +#, fuzzy msgid "Login account" -msgstr "" +msgstr "登录访问控制" #: tickets/models/ticket/login_confirm.py:12 msgid "Login datetime" -msgstr "" +msgstr "登录日期" #: tickets/notifications.py:63 msgid "Ticket basic info" -msgstr "" +msgstr "工单基本信息" #: tickets/notifications.py:64 msgid "Ticket applied info" -msgstr "" +msgstr "工单申请信息" #: tickets/notifications.py:109 msgid "Your has a new ticket, applicant - {}" -msgstr "" +msgstr "你有一个新的工单, 申请人 - {}" #: tickets/notifications.py:113 msgid "{}: New Ticket - {} ({})" -msgstr "" +msgstr "新工单 - {} ({})" #: tickets/notifications.py:157 msgid "Your ticket has been processed, processor - {}" -msgstr "" +msgstr "你的工单已被处理, 处理人 - {}" #: tickets/notifications.py:161 msgid "Ticket has processed - {} ({})" -msgstr "" +msgstr "你的工单已被处理, 处理人 - {} ({})" #: tickets/serializers/flow.py:21 msgid "Assignees display" -msgstr "" +msgstr "受理人名称" #: tickets/serializers/flow.py:47 msgid "Please select the Assignees" -msgstr "" +msgstr "请选择受理人" #: tickets/serializers/flow.py:75 msgid "The current organization type already exists" -msgstr "" +msgstr "当前组织已存在该类型" #: tickets/serializers/super_ticket.py:11 msgid "Processor" -msgstr "" +msgstr "处理人" #: tickets/serializers/ticket/apply_asset.py:20 +#, fuzzy msgid "Apply actions" -msgstr "" +msgstr "申请应用" #: tickets/serializers/ticket/common.py:15 #: tickets/serializers/ticket/common.py:77 msgid "Created by ticket ({}-{})" -msgstr "" +msgstr "通过工单创建 ({}-{})" #: tickets/serializers/ticket/common.py:67 msgid "The expiration date should be greater than the start date" -msgstr "" +msgstr "过期时间要大于开始时间" #: tickets/serializers/ticket/common.py:84 msgid "Permission named `{}` already exists" -msgstr "" +msgstr "授权名称 `{}` 已存在" #: tickets/serializers/ticket/ticket.py:96 msgid "The ticket flow `{}` does not exist" -msgstr "" +msgstr "工单流程 `{}` 不存在" #: tickets/templates/tickets/_msg_ticket.html:20 msgid "View details" -msgstr "" +msgstr "查看详情" #: tickets/templates/tickets/_msg_ticket.html:25 msgid "Direct approval" -msgstr "" +msgstr "直接批准" #: tickets/templates/tickets/approve_check_password.html:11 msgid "Ticket information" -msgstr "" +msgstr "工单信息" #: tickets/templates/tickets/approve_check_password.html:29 #: tickets/views/approve.py:38 msgid "Ticket approval" -msgstr "" +msgstr "工单审批" #: tickets/templates/tickets/approve_check_password.html:45 msgid "Approval" -msgstr "" +msgstr "同意" #: tickets/templates/tickets/approve_check_password.html:54 msgid "Go Login" -msgstr "" +msgstr "去登录" #: tickets/views/approve.py:39 msgid "" "This ticket does not exist, the process has ended, or this link has expired" -msgstr "" +msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期" #: tickets/views/approve.py:68 msgid "Click the button below to approve or reject" -msgstr "" +msgstr "点击下方按钮同意或者拒绝" #: tickets/views/approve.py:70 msgid "After successful authentication, this ticket can be approved directly" -msgstr "" +msgstr "认证成功后,工单可直接审批" #: tickets/views/approve.py:92 msgid "Illegal approval action" -msgstr "" +msgstr "无效的审批动作" #: tickets/views/approve.py:105 msgid "This user is not authorized to approve this ticket" -msgstr "" +msgstr "此用户无权审批此工单" #: users/api/user.py:183 msgid "Could not reset self otp, use profile reset instead" -msgstr "" +msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置" #: users/apps.py:9 msgid "Users" -msgstr "" +msgstr "用户管理" #: users/const.py:10 msgid "System administrator" -msgstr "" +msgstr "系统管理员" #: users/const.py:11 msgid "System auditor" -msgstr "" +msgstr "系统审计员" #: users/const.py:12 msgid "Organization administrator" -msgstr "" +msgstr "组织管理员" #: users/const.py:13 msgid "Organization auditor" -msgstr "" +msgstr "组织审计员" #: users/const.py:18 msgid "Reset link will be generated and sent to the user" -msgstr "" +msgstr "生成重置密码链接,通过邮件发送给用户" #: users/const.py:19 msgid "Set password" -msgstr "" +msgstr "设置密码" #: users/exceptions.py:10 msgid "MFA not enabled" -msgstr "" +msgstr "MFA 多因子认证没有开启" #: users/exceptions.py:20 msgid "MFA method not support" -msgstr "" +msgstr "不支持该 MFA 方式" #: users/forms/profile.py:50 msgid "" @@ -5766,10 +5956,12 @@ msgid "" "in. you can also directly bind in \"personal information -> quick " "modification -> change MFA Settings\"!" msgstr "" +"启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速" +"修改->设置 MFA 多因子认证)中直接绑定!" #: users/forms/profile.py:61 msgid "* Enable MFA to make the account more secure." -msgstr "" +msgstr "* 启用 MFA 多因子认证,使账号更加安全。" #: users/forms/profile.py:70 msgid "" @@ -5777,710 +5969,714 @@ msgid "" "and key sensitive information properly. (for example: setting complex " "password, enabling MFA)" msgstr "" +"为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息;(如:" +"设置复杂密码,并启用 MFA 多因子认证)" #: users/forms/profile.py:77 msgid "Finish" -msgstr "" +msgstr "完成" #: users/forms/profile.py:84 msgid "New password" -msgstr "" +msgstr "新密码" #: users/forms/profile.py:89 msgid "Confirm password" -msgstr "" +msgstr "确认密码" #: users/forms/profile.py:97 msgid "Password does not match" -msgstr "" +msgstr "密码不一致" #: users/forms/profile.py:118 msgid "Old password" -msgstr "" +msgstr "原来密码" #: users/forms/profile.py:128 msgid "Old password error" -msgstr "" +msgstr "原来密码错误" #: users/forms/profile.py:138 msgid "Automatically configure and download the SSH key" -msgstr "" +msgstr "自动配置并下载SSH密钥" #: users/forms/profile.py:140 msgid "ssh public key" -msgstr "" +msgstr "SSH公钥" #: users/forms/profile.py:141 msgid "ssh-rsa AAAA..." -msgstr "" +msgstr "ssh-rsa AAAA..." #: users/forms/profile.py:142 msgid "Paste your id_rsa.pub here." -msgstr "" +msgstr "复制你的公钥到这里" #: users/forms/profile.py:155 msgid "Public key should not be the same as your old one." -msgstr "" +msgstr "不能和原来的密钥相同" #: users/forms/profile.py:159 users/serializers/profile.py:100 #: users/serializers/profile.py:183 users/serializers/profile.py:210 msgid "Not a valid ssh public key" -msgstr "" +msgstr "SSH密钥不合法" #: users/forms/profile.py:170 users/models/user.py:708 msgid "Public key" -msgstr "" +msgstr "SSH公钥" #: users/models/user.py:561 msgid "Force enable" -msgstr "" +msgstr "强制启用" #: users/models/user.py:631 msgid "Local" -msgstr "" +msgstr "数据库" #: users/models/user.py:687 users/serializers/user.py:204 msgid "Is service account" -msgstr "" +msgstr "服务账号" #: users/models/user.py:689 msgid "Avatar" -msgstr "" +msgstr "头像" #: users/models/user.py:692 msgid "Wechat" -msgstr "" +msgstr "微信" #: users/models/user.py:695 msgid "Phone" -msgstr "" +msgstr "手机" #: users/models/user.py:701 msgid "OTP secret key" -msgstr "" +msgstr "OTP 秘钥" #: users/models/user.py:705 msgid "Private key" -msgstr "" +msgstr "ssh私钥" #: users/models/user.py:711 msgid "Secret key" -msgstr "" +msgstr "Secret key" #: users/models/user.py:716 users/serializers/profile.py:149 #: users/serializers/user.py:201 msgid "Is first login" -msgstr "" +msgstr "首次登录" #: users/models/user.py:727 msgid "Source" -msgstr "" +msgstr "来源" #: users/models/user.py:731 msgid "Date password last updated" -msgstr "" +msgstr "最后更新密码日期" #: users/models/user.py:734 msgid "Need update password" -msgstr "" +msgstr "需要更新密码" #: users/models/user.py:909 msgid "Can invite user" -msgstr "" +msgstr "可以邀请用户" #: users/models/user.py:910 msgid "Can remove user" -msgstr "" +msgstr "可以移除用户" #: users/models/user.py:911 msgid "Can match user" -msgstr "" +msgstr "可以匹配用户" #: users/models/user.py:920 msgid "Administrator" -msgstr "" +msgstr "管理员" #: users/models/user.py:923 msgid "Administrator is the super user of system" -msgstr "" +msgstr "Administrator是初始的超级管理员" #: users/models/user.py:948 msgid "User password history" -msgstr "" +msgstr "用户密码历史" #: users/notifications.py:55 #: users/templates/users/_msg_password_expire_reminder.html:17 #: users/templates/users/reset_password.html:5 #: users/templates/users/reset_password.html:6 msgid "Reset password" -msgstr "" +msgstr "重置密码" #: users/notifications.py:85 users/views/profile/reset.py:194 msgid "Reset password success" -msgstr "" +msgstr "重置密码成功" #: users/notifications.py:117 msgid "Reset public key success" -msgstr "" +msgstr "重置公钥成功" #: users/notifications.py:143 msgid "Password is about expire" -msgstr "" +msgstr "密码即将过期" #: users/notifications.py:171 msgid "Account is about expire" -msgstr "" +msgstr "账号即将过期" #: users/notifications.py:193 msgid "Reset SSH Key" -msgstr "" +msgstr "重置 SSH 密钥" #: users/notifications.py:214 msgid "Reset MFA" -msgstr "" +msgstr "重置 MFA" #: users/serializers/profile.py:30 msgid "The old password is incorrect" -msgstr "" +msgstr "旧密码错误" #: users/serializers/profile.py:37 users/serializers/profile.py:197 msgid "Password does not match security rules" -msgstr "" +msgstr "密码不满足安全规则" #: users/serializers/profile.py:41 msgid "The new password cannot be the last {} passwords" -msgstr "" +msgstr "新密码不能是最近 {} 次的密码" #: users/serializers/profile.py:49 users/serializers/profile.py:71 msgid "The newly set password is inconsistent" -msgstr "" +msgstr "两次密码不一致" #: users/serializers/user.py:30 msgid "System roles" -msgstr "" +msgstr "系统角色" #: users/serializers/user.py:35 msgid "Org roles" -msgstr "" +msgstr "组织角色" #: users/serializers/user.py:38 msgid "System roles display" -msgstr "" +msgstr "系统角色显示" #: users/serializers/user.py:40 msgid "Org roles display" -msgstr "" +msgstr "组织角色显示" #: users/serializers/user.py:90 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" -msgstr "" +msgstr "密码策略" #: users/serializers/user.py:92 msgid "MFA enabled" -msgstr "" +msgstr "MFA 已启用" #: users/serializers/user.py:94 msgid "MFA force enabled" -msgstr "" +msgstr "强制 MFA" #: users/serializers/user.py:97 msgid "MFA level display" -msgstr "" +msgstr "MFA 等级名称" #: users/serializers/user.py:99 msgid "Login blocked" -msgstr "" +msgstr "登录被阻塞" #: users/serializers/user.py:102 msgid "Can public key authentication" -msgstr "" +msgstr "能否公钥认证" #: users/serializers/user.py:206 msgid "Avatar url" -msgstr "" +msgstr "头像路径" #: users/serializers/user.py:208 msgid "Groups name" -msgstr "" +msgstr "用户组名" #: users/serializers/user.py:209 msgid "Source name" -msgstr "" +msgstr "用户来源名" #: users/serializers/user.py:210 msgid "Organization role name" -msgstr "" +msgstr "组织角色名称" #: users/serializers/user.py:211 msgid "Super role name" -msgstr "" +msgstr "超级角色名称" #: users/serializers/user.py:212 msgid "Total role name" -msgstr "" +msgstr "汇总角色名称" #: users/serializers/user.py:214 msgid "Is wecom bound" -msgstr "" +msgstr "是否绑定了企业微信" #: users/serializers/user.py:215 msgid "Is dingtalk bound" -msgstr "" +msgstr "是否绑定了钉钉" #: users/serializers/user.py:216 msgid "Is feishu bound" -msgstr "" +msgstr "是否绑定了飞书" #: users/serializers/user.py:217 msgid "Is OTP bound" -msgstr "" +msgstr "是否绑定了虚拟 MFA" #: users/serializers/user.py:219 msgid "System role name" -msgstr "" +msgstr "系统角色名称" #: users/serializers/user.py:325 msgid "Select users" -msgstr "" +msgstr "选择用户" #: users/serializers/user.py:326 msgid "For security, only list several users" -msgstr "" +msgstr "为了安全,仅列出几个用户" #: users/serializers/user.py:362 msgid "name not unique" -msgstr "" +msgstr "名称重复" #: users/templates/users/_msg_account_expire_reminder.html:7 msgid "Your account will expire in" -msgstr "" +msgstr "您的账号即将过期" #: users/templates/users/_msg_account_expire_reminder.html:8 msgid "" "In order not to affect your normal work, please contact the administrator " "for confirmation." msgstr "" +"为了不影响您正常工作,请联系管理员确认。\n" +" " #: users/templates/users/_msg_password_expire_reminder.html:7 msgid "Your password will expire in" -msgstr "" +msgstr "您的密码将过期" #: users/templates/users/_msg_password_expire_reminder.html:8 msgid "" "For your account security, please click on the link below to update your " "password in time" -msgstr "" +msgstr "为了您的账号安全,请点击下面的链接及时更新密码" #: users/templates/users/_msg_password_expire_reminder.html:11 msgid "Click here update password" -msgstr "" +msgstr "点击这里更新密码" #: users/templates/users/_msg_password_expire_reminder.html:16 msgid "If your password has expired, please click the link below to" -msgstr "" +msgstr "如果你的密码已过期,请点击" #: users/templates/users/_msg_reset_mfa.html:7 msgid "Your MFA has been reset by site administrator" -msgstr "" +msgstr "你的 MFA 已经被管理员重置" #: users/templates/users/_msg_reset_mfa.html:8 #: users/templates/users/_msg_reset_ssh_key.html:8 msgid "Please click the link below to set" -msgstr "" +msgstr "请点击下面链接设置" #: users/templates/users/_msg_reset_mfa.html:11 #: users/templates/users/_msg_reset_ssh_key.html:11 msgid "Click here set" -msgstr "" +msgstr "点击这里设置" #: users/templates/users/_msg_reset_ssh_key.html:7 msgid "Your ssh public key has been reset by site administrator" -msgstr "" +msgstr "你的 SSH 密钥已经被管理员重置" #: users/templates/users/_msg_user_created.html:15 msgid "click here to set your password" -msgstr "" +msgstr "点击这里设置密码" #: users/templates/users/forgot_password.html:32 msgid "Input your email account, that will send a email to your" -msgstr "" +msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" #: users/templates/users/forgot_password.html:35 msgid "" "Enter your mobile number and a verification code will be sent to your phone" -msgstr "" +msgstr "输入您的手机号码,验证码将发送到您的手机" #: users/templates/users/forgot_password.html:57 msgid "Email account" -msgstr "" +msgstr "邮箱账号" #: users/templates/users/forgot_password.html:61 msgid "Mobile number" -msgstr "" +msgstr "手机号码" #: users/templates/users/forgot_password.html:68 msgid "Send" -msgstr "" +msgstr "发送" #: users/templates/users/forgot_password.html:72 #: users/templates/users/forgot_password_previewing.html:30 msgid "Submit" -msgstr "" +msgstr "提交" #: users/templates/users/forgot_password_previewing.html:21 msgid "Please enter the username for which you want to retrieve the password" -msgstr "" +msgstr "请输入您需要找回密码的用户名" #: users/templates/users/mfa_setting.html:24 msgid "Enable MFA" -msgstr "" +msgstr "启用 MFA 多因子认证" #: users/templates/users/mfa_setting.html:30 msgid "MFA force enable, cannot disable" -msgstr "" +msgstr "MFA 已强制启用,无法禁用" #: users/templates/users/mfa_setting.html:48 msgid "MFA setting" -msgstr "" +msgstr "设置 MFA 多因子认证" #: users/templates/users/reset_password.html:23 msgid "Your password must satisfy" -msgstr "" +msgstr "您的密码必须满足:" #: users/templates/users/reset_password.html:24 msgid "Password strength" -msgstr "" +msgstr "密码强度:" #: users/templates/users/reset_password.html:48 msgid "Very weak" -msgstr "" +msgstr "很弱" #: users/templates/users/reset_password.html:49 msgid "Weak" -msgstr "" +msgstr "弱" #: users/templates/users/reset_password.html:51 msgid "Medium" -msgstr "" +msgstr "一般" #: users/templates/users/reset_password.html:52 msgid "Strong" -msgstr "" +msgstr "强" #: users/templates/users/reset_password.html:53 msgid "Very strong" -msgstr "" +msgstr "很强" #: users/templates/users/user_otp_check_password.html:6 msgid "Enable OTP" -msgstr "" +msgstr "启用 MFA(OTP)" #: users/templates/users/user_otp_enable_bind.html:6 msgid "Bind one-time password authenticator" -msgstr "" +msgstr "绑定MFA验证器" #: users/templates/users/user_otp_enable_bind.html:13 msgid "" "Use the MFA Authenticator application to scan the following qr code for a 6-" "bit verification code" -msgstr "" +msgstr "使用 MFA 验证器应用扫描以下二维码,获取6位验证码" #: users/templates/users/user_otp_enable_bind.html:22 #: users/templates/users/user_verify_mfa.html:27 msgid "Six figures" -msgstr "" +msgstr "6 位数字" #: users/templates/users/user_otp_enable_install_app.html:6 msgid "Install app" -msgstr "" +msgstr "安装应用" #: users/templates/users/user_otp_enable_install_app.html:13 msgid "" "Download and install the MFA Authenticator application on your phone or " "applet of WeChat" -msgstr "" +msgstr "请在手机端或微信小程序下载并安装 MFA 验证器应用" #: users/templates/users/user_otp_enable_install_app.html:18 msgid "Android downloads" -msgstr "" +msgstr "Android手机下载" #: users/templates/users/user_otp_enable_install_app.html:23 msgid "iPhone downloads" -msgstr "" +msgstr "iPhone手机下载" #: users/templates/users/user_otp_enable_install_app.html:26 msgid "" "After installation, click the next step to enter the binding page (if " "installed, go to the next step directly)." -msgstr "" +msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)" #: users/templates/users/user_password_verify.html:8 #: users/templates/users/user_password_verify.html:9 msgid "Verify password" -msgstr "" +msgstr "校验密码" #: users/templates/users/user_verify_mfa.html:9 msgid "Authenticate" -msgstr "" +msgstr "验证身份" #: users/templates/users/user_verify_mfa.html:15 msgid "" "The account protection has been opened, please complete the following " "operations according to the prompts" -msgstr "" +msgstr "账号保护已开启,请根据提示完成以下操作" #: users/templates/users/user_verify_mfa.html:17 msgid "Open MFA Authenticator and enter the 6-bit dynamic code" -msgstr "" +msgstr "请打开 MFA 验证器,输入 6 位动态码" #: users/views/profile/otp.py:87 msgid "Already bound" -msgstr "" +msgstr "已经绑定" #: users/views/profile/otp.py:88 msgid "MFA already bound, disable first, then bound" -msgstr "" +msgstr "MFA(OTP) 已经绑定,请先禁用,再绑定" #: users/views/profile/otp.py:115 msgid "OTP enable success" -msgstr "" +msgstr "MFA(OTP) 启用成功" #: users/views/profile/otp.py:116 msgid "OTP enable success, return login page" -msgstr "" +msgstr "MFA(OTP) 启用成功,返回到登录页面" #: users/views/profile/otp.py:158 msgid "Disable OTP" -msgstr "" +msgstr "禁用虚拟 MFA(OTP)" #: users/views/profile/otp.py:164 msgid "OTP disable success" -msgstr "" +msgstr "MFA(OTP) 禁用成功" #: users/views/profile/otp.py:165 msgid "OTP disable success, return login page" -msgstr "" +msgstr "MFA(OTP) 禁用成功,返回登录页面" #: users/views/profile/password.py:36 users/views/profile/password.py:41 msgid "Password invalid" -msgstr "" +msgstr "用户名或密码无效" #: users/views/profile/reset.py:47 msgid "" "Non-local users can log in only from third-party platforms and cannot change " "their passwords: {}" -msgstr "" +msgstr "非本地用户仅允许从第三方平台登录,不支持修改密码: {}" #: users/views/profile/reset.py:149 users/views/profile/reset.py:160 msgid "Token invalid or expired" -msgstr "" +msgstr "Token错误或失效" #: users/views/profile/reset.py:165 msgid "User auth from {}, go there change password" -msgstr "" +msgstr "用户认证源来自 {}, 请去相应系统修改密码" #: users/views/profile/reset.py:172 msgid "* Your password does not meet the requirements" -msgstr "" +msgstr "* 您的密码不符合要求" #: users/views/profile/reset.py:178 msgid "* The new password cannot be the last {} passwords" -msgstr "" +msgstr "* 新密码不能是最近 {} 次的密码" #: users/views/profile/reset.py:195 msgid "Reset password success, return to login page" -msgstr "" +msgstr "重置密码成功,返回到登录页面" #: xpack/apps.py:8 msgid "XPACK" -msgstr "" +msgstr "XPack" #: xpack/plugins/change_auth_plan/meta.py:9 #: xpack/plugins/change_auth_plan/models/asset.py:124 msgid "Change auth plan" -msgstr "" +msgstr "改密计划" #: xpack/plugins/change_auth_plan/models/app.py:45 #: xpack/plugins/change_auth_plan/models/app.py:94 msgid "Application change auth plan" -msgstr "" +msgstr "应用改密计划" #: xpack/plugins/change_auth_plan/models/app.py:98 #: xpack/plugins/change_auth_plan/models/app.py:150 msgid "Application change auth plan execution" -msgstr "" +msgstr "应用改密计划执行" #: xpack/plugins/change_auth_plan/models/app.py:143 msgid "App" -msgstr "" +msgstr "应用" #: xpack/plugins/change_auth_plan/models/app.py:155 msgid "Application change auth plan task" -msgstr "" +msgstr "应用改密计划任务" #: xpack/plugins/change_auth_plan/models/app.py:179 #: xpack/plugins/change_auth_plan/models/asset.py:264 msgid "Password cannot be set to blank, exit. " -msgstr "" +msgstr "密码不能设置为空, 退出. " #: xpack/plugins/change_auth_plan/models/asset.py:68 msgid "Asset change auth plan" -msgstr "" +msgstr "资产改密计划" #: xpack/plugins/change_auth_plan/models/asset.py:135 msgid "Asset change auth plan execution" -msgstr "" +msgstr "资产改密计划执行" #: xpack/plugins/change_auth_plan/models/asset.py:211 msgid "Change auth plan execution" -msgstr "" +msgstr "改密计划执行" #: xpack/plugins/change_auth_plan/models/asset.py:218 msgid "Asset change auth plan task" -msgstr "" +msgstr "资产改密计划任务" #: xpack/plugins/change_auth_plan/models/asset.py:253 msgid "This asset does not have a privileged user set: " -msgstr "" +msgstr "该资产没有设置特权用户: " #: xpack/plugins/change_auth_plan/models/asset.py:259 msgid "" "The password and key of the current asset privileged user cannot be changed: " -msgstr "" +msgstr "不能更改当前资产特权用户的密码及密钥: " #: xpack/plugins/change_auth_plan/models/asset.py:270 msgid "Public key cannot be set to null, exit. " -msgstr "" +msgstr "公钥不能设置为空, 退出. " #: xpack/plugins/change_auth_plan/models/base.py:114 msgid "Change auth plan snapshot" -msgstr "" +msgstr "改密计划快照" #: xpack/plugins/change_auth_plan/models/base.py:184 msgid "Preflight check" -msgstr "" +msgstr "改密前的校验" #: xpack/plugins/change_auth_plan/models/base.py:185 msgid "Change auth" -msgstr "" +msgstr "执行改密" #: xpack/plugins/change_auth_plan/models/base.py:186 msgid "Verify auth" -msgstr "" +msgstr "验证密码/密钥" #: xpack/plugins/change_auth_plan/models/base.py:187 msgid "Keep auth" -msgstr "" +msgstr "保存密码/密钥" #: xpack/plugins/change_auth_plan/models/base.py:195 msgid "Step" -msgstr "" +msgstr "步骤" #: xpack/plugins/change_auth_plan/serializers/asset.py:30 msgid "Change Password" -msgstr "" +msgstr "更改密码" #: xpack/plugins/change_auth_plan/serializers/asset.py:31 msgid "Change SSH Key" -msgstr "" +msgstr "修改 SSH Key" #: xpack/plugins/change_auth_plan/serializers/base.py:44 msgid "Run times" -msgstr "" +msgstr "执行次数" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 msgid "After many attempts to change the secret, it still failed" -msgstr "" +msgstr "多次尝试改密后, 依然失败" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:255 msgid "Invalid/incorrect password" -msgstr "" +msgstr "无效/错误 密码" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:257 msgid "Failed to connect to the host" -msgstr "" +msgstr "连接主机失败" #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:259 msgid "Data could not be sent to remote" -msgstr "" +msgstr "无法将数据发送到远程" #: xpack/plugins/cloud/api.py:40 msgid "Test connection successful" -msgstr "" +msgstr "测试成功" #: xpack/plugins/cloud/api.py:42 msgid "Test connection failed: {}" -msgstr "" +msgstr "测试连接失败:{}" #: xpack/plugins/cloud/const.py:8 msgid "Alibaba Cloud" -msgstr "" +msgstr "阿里云" #: xpack/plugins/cloud/const.py:9 msgid "AWS (International)" -msgstr "" +msgstr "AWS (国际)" #: xpack/plugins/cloud/const.py:10 msgid "AWS (China)" -msgstr "" +msgstr "AWS (中国)" #: xpack/plugins/cloud/const.py:11 msgid "Azure (China)" -msgstr "" +msgstr "Azure (中国)" #: xpack/plugins/cloud/const.py:12 msgid "Azure (International)" -msgstr "" +msgstr "Azure (国际)" #: xpack/plugins/cloud/const.py:14 msgid "Baidu Cloud" -msgstr "" +msgstr "百度云" #: xpack/plugins/cloud/const.py:15 msgid "JD Cloud" -msgstr "" +msgstr "京东云" #: xpack/plugins/cloud/const.py:16 msgid "KingSoft Cloud" -msgstr "" +msgstr "金山云" #: xpack/plugins/cloud/const.py:17 msgid "Tencent Cloud" -msgstr "" +msgstr "腾讯云" #: xpack/plugins/cloud/const.py:18 msgid "Tencent Cloud (Lighthouse)" -msgstr "" +msgstr "腾讯云(轻量服务器应用)" #: xpack/plugins/cloud/const.py:19 msgid "VMware" -msgstr "" +msgstr "VMware" #: xpack/plugins/cloud/const.py:20 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" -msgstr "" +msgstr "Nutanix" #: xpack/plugins/cloud/const.py:21 msgid "Huawei Private Cloud" -msgstr "" +msgstr "华为私有云" #: xpack/plugins/cloud/const.py:22 msgid "Qingyun Private Cloud" -msgstr "" +msgstr "青云私有云" #: xpack/plugins/cloud/const.py:23 msgid "CTYun Private Cloud" -msgstr "" +msgstr "天翼私有云" #: xpack/plugins/cloud/const.py:24 msgid "OpenStack" -msgstr "" +msgstr "OpenStack" #: xpack/plugins/cloud/const.py:25 msgid "Google Cloud Platform" -msgstr "" +msgstr "谷歌云" #: xpack/plugins/cloud/const.py:26 msgid "Fusion Compute" @@ -6488,374 +6684,374 @@ msgstr "" #: xpack/plugins/cloud/const.py:31 msgid "Private IP" -msgstr "" +msgstr "私有IP" #: xpack/plugins/cloud/const.py:32 msgid "Public IP" -msgstr "" +msgstr "公网IP" #: xpack/plugins/cloud/const.py:36 msgid "Instance name" -msgstr "" +msgstr "实例名称" #: xpack/plugins/cloud/const.py:37 msgid "Instance name and Partial IP" -msgstr "" +msgstr "实例名称和部分IP" #: xpack/plugins/cloud/const.py:42 msgid "Succeed" -msgstr "" +msgstr "成功" #: xpack/plugins/cloud/const.py:46 msgid "Unsync" -msgstr "" +msgstr "未同步" #: xpack/plugins/cloud/const.py:47 msgid "New Sync" -msgstr "" +msgstr "新同步" #: xpack/plugins/cloud/const.py:48 msgid "Synced" -msgstr "" +msgstr "已同步" #: xpack/plugins/cloud/const.py:49 msgid "Released" -msgstr "" +msgstr "已释放" #: xpack/plugins/cloud/meta.py:9 msgid "Cloud center" -msgstr "" +msgstr "云管中心" #: xpack/plugins/cloud/models.py:32 msgid "Provider" -msgstr "" +msgstr "云服务商" #: xpack/plugins/cloud/models.py:36 msgid "Validity" -msgstr "" +msgstr "有效" #: xpack/plugins/cloud/models.py:41 msgid "Cloud account" -msgstr "" +msgstr "云账号" #: xpack/plugins/cloud/models.py:43 msgid "Test cloud account" -msgstr "" +msgstr "测试云账号" #: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:38 msgid "Regions" -msgstr "" +msgstr "地域" #: xpack/plugins/cloud/models.py:93 msgid "Hostname strategy" -msgstr "" +msgstr "主机名策略" #: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:72 msgid "Unix admin user" -msgstr "" +msgstr "Unix 管理员" #: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:73 msgid "Windows admin user" -msgstr "" +msgstr "Windows 管理员" #: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:46 msgid "IP network segment group" -msgstr "" +msgstr "IP网段组" #: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:51 msgid "Sync IP type" -msgstr "" +msgstr "同步IP类型" #: xpack/plugins/cloud/models.py:118 xpack/plugins/cloud/serializers/task.py:76 msgid "Always update" -msgstr "" +msgstr "总是更新" #: xpack/plugins/cloud/models.py:124 msgid "Date last sync" -msgstr "" +msgstr "最后同步日期" #: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 msgid "Sync instance task" -msgstr "" +msgstr "同步实例任务" #: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 msgid "Date sync" -msgstr "" +msgstr "同步日期" #: xpack/plugins/cloud/models.py:185 msgid "Sync instance task execution" -msgstr "" +msgstr "同步实例任务执行" #: xpack/plugins/cloud/models.py:209 msgid "Sync task" -msgstr "" +msgstr "同步任务" #: xpack/plugins/cloud/models.py:213 msgid "Sync instance task history" -msgstr "" +msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:216 msgid "Instance" -msgstr "" +msgstr "实例" #: xpack/plugins/cloud/models.py:233 msgid "Sync instance detail" -msgstr "" +msgstr "同步实例详情" #: xpack/plugins/cloud/providers/aws_international.py:17 msgid "China (Beijing)" -msgstr "" +msgstr "中国(北京)" #: xpack/plugins/cloud/providers/aws_international.py:18 msgid "China (Ningxia)" -msgstr "" +msgstr "中国(宁夏)" #: xpack/plugins/cloud/providers/aws_international.py:21 msgid "US East (Ohio)" -msgstr "" +msgstr "美国东部(俄亥俄州)" #: xpack/plugins/cloud/providers/aws_international.py:22 msgid "US East (N. Virginia)" -msgstr "" +msgstr "美国东部(弗吉尼亚北部)" #: xpack/plugins/cloud/providers/aws_international.py:23 msgid "US West (N. California)" -msgstr "" +msgstr "美国西部(加利福尼亚北部)" #: xpack/plugins/cloud/providers/aws_international.py:24 msgid "US West (Oregon)" -msgstr "" +msgstr "美国西部(俄勒冈)" #: xpack/plugins/cloud/providers/aws_international.py:25 msgid "Africa (Cape Town)" -msgstr "" +msgstr "非洲(开普敦)" #: xpack/plugins/cloud/providers/aws_international.py:26 msgid "Asia Pacific (Hong Kong)" -msgstr "" +msgstr "亚太地区(香港)" #: xpack/plugins/cloud/providers/aws_international.py:27 msgid "Asia Pacific (Mumbai)" -msgstr "" +msgstr "亚太地区(孟买)" #: xpack/plugins/cloud/providers/aws_international.py:28 msgid "Asia Pacific (Osaka-Local)" -msgstr "" +msgstr "亚太区域(大阪当地)" #: xpack/plugins/cloud/providers/aws_international.py:29 msgid "Asia Pacific (Seoul)" -msgstr "" +msgstr "亚太区域(首尔)" #: xpack/plugins/cloud/providers/aws_international.py:30 msgid "Asia Pacific (Singapore)" -msgstr "" +msgstr "亚太区域(新加坡)" #: xpack/plugins/cloud/providers/aws_international.py:31 msgid "Asia Pacific (Sydney)" -msgstr "" +msgstr "亚太区域(悉尼)" #: xpack/plugins/cloud/providers/aws_international.py:32 msgid "Asia Pacific (Tokyo)" -msgstr "" +msgstr "亚太区域(东京)" #: xpack/plugins/cloud/providers/aws_international.py:33 msgid "Canada (Central)" -msgstr "" +msgstr "加拿大(中部)" #: xpack/plugins/cloud/providers/aws_international.py:34 msgid "Europe (Frankfurt)" -msgstr "" +msgstr "欧洲(法兰克福)" #: xpack/plugins/cloud/providers/aws_international.py:35 msgid "Europe (Ireland)" -msgstr "" +msgstr "欧洲(爱尔兰)" #: xpack/plugins/cloud/providers/aws_international.py:36 msgid "Europe (London)" -msgstr "" +msgstr "欧洲(伦敦)" #: xpack/plugins/cloud/providers/aws_international.py:37 msgid "Europe (Milan)" -msgstr "" +msgstr "欧洲(米兰)" #: xpack/plugins/cloud/providers/aws_international.py:38 msgid "Europe (Paris)" -msgstr "" +msgstr "欧洲(巴黎)" #: xpack/plugins/cloud/providers/aws_international.py:39 msgid "Europe (Stockholm)" -msgstr "" +msgstr "欧洲(斯德哥尔摩)" #: xpack/plugins/cloud/providers/aws_international.py:40 msgid "Middle East (Bahrain)" -msgstr "" +msgstr "中东(巴林)" #: xpack/plugins/cloud/providers/aws_international.py:41 msgid "South America (São Paulo)" -msgstr "" +msgstr "南美洲(圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 #: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" -msgstr "" +msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 #: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" -msgstr "" +msgstr "华南-广州" #: xpack/plugins/cloud/providers/baiducloud.py:56 msgid "CN East-Suzhou" -msgstr "" +msgstr "华东-苏州" #: xpack/plugins/cloud/providers/baiducloud.py:57 #: xpack/plugins/cloud/providers/huaweicloud.py:48 msgid "CN-Hong Kong" -msgstr "" +msgstr "中国-香港" #: xpack/plugins/cloud/providers/baiducloud.py:58 msgid "CN Center-Wuhan" -msgstr "" +msgstr "华中-武汉" #: xpack/plugins/cloud/providers/baiducloud.py:59 msgid "CN North-Baoding" -msgstr "" +msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 #: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" -msgstr "" +msgstr "华东-上海" #: xpack/plugins/cloud/providers/baiducloud.py:61 #: xpack/plugins/cloud/providers/huaweicloud.py:47 msgid "AP-Singapore" -msgstr "" +msgstr "亚太-新加坡" #: xpack/plugins/cloud/providers/huaweicloud.py:35 msgid "AF-Johannesburg" -msgstr "" +msgstr "非洲-约翰内斯堡" #: xpack/plugins/cloud/providers/huaweicloud.py:36 msgid "CN North-Beijing4" -msgstr "" +msgstr "华北-北京4" #: xpack/plugins/cloud/providers/huaweicloud.py:37 msgid "CN North-Beijing1" -msgstr "" +msgstr "华北-北京1" #: xpack/plugins/cloud/providers/huaweicloud.py:38 msgid "CN East-Shanghai2" -msgstr "" +msgstr "华东-上海2" #: xpack/plugins/cloud/providers/huaweicloud.py:39 msgid "CN East-Shanghai1" -msgstr "" +msgstr "华东-上海1" #: xpack/plugins/cloud/providers/huaweicloud.py:41 msgid "LA-Mexico City1" -msgstr "" +msgstr "拉美-墨西哥城一" #: xpack/plugins/cloud/providers/huaweicloud.py:42 msgid "LA-Santiago" -msgstr "" +msgstr "拉美-圣地亚哥" #: xpack/plugins/cloud/providers/huaweicloud.py:43 msgid "LA-Sao Paulo1" -msgstr "" +msgstr "拉美-圣保罗一" #: xpack/plugins/cloud/providers/huaweicloud.py:44 msgid "EU-Paris" -msgstr "" +msgstr "欧洲-巴黎" #: xpack/plugins/cloud/providers/huaweicloud.py:45 msgid "CN Southwest-Guiyang1" -msgstr "" +msgstr "西南-贵阳1" #: xpack/plugins/cloud/providers/huaweicloud.py:46 msgid "AP-Bangkok" -msgstr "" +msgstr "亚太-曼谷" #: xpack/plugins/cloud/providers/huaweicloud.py:50 msgid "CN Northeast-Dalian" -msgstr "" +msgstr "华北-大连" #: xpack/plugins/cloud/providers/huaweicloud.py:51 msgid "CN North-Ulanqab1" -msgstr "" +msgstr "华北-乌兰察布一" #: xpack/plugins/cloud/providers/huaweicloud.py:52 msgid "CN South-Guangzhou-InvitationOnly" -msgstr "" +msgstr "华南-广州-友好用户环境" #: xpack/plugins/cloud/providers/jdcloud.py:128 msgid "CN East-Suqian" -msgstr "" +msgstr "华东-宿迁" #: xpack/plugins/cloud/serializers/account.py:65 msgid "Validity display" -msgstr "" +msgstr "有效性显示" #: xpack/plugins/cloud/serializers/account.py:66 msgid "Provider display" -msgstr "" +msgstr "服务商显示" #: xpack/plugins/cloud/serializers/account_attrs.py:35 msgid "Client ID" -msgstr "" +msgstr "客户端 ID" #: xpack/plugins/cloud/serializers/account_attrs.py:41 msgid "Tenant ID" -msgstr "" +msgstr "租户 ID" #: xpack/plugins/cloud/serializers/account_attrs.py:44 msgid "Subscription ID" -msgstr "" +msgstr "订阅 ID" #: xpack/plugins/cloud/serializers/account_attrs.py:95 #: xpack/plugins/cloud/serializers/account_attrs.py:100 #: xpack/plugins/cloud/serializers/account_attrs.py:116 #: xpack/plugins/cloud/serializers/account_attrs.py:141 msgid "API Endpoint" -msgstr "" +msgstr "API 端点" #: xpack/plugins/cloud/serializers/account_attrs.py:106 msgid "Auth url" -msgstr "" +msgstr "认证地址" #: xpack/plugins/cloud/serializers/account_attrs.py:107 msgid "eg: http://openstack.example.com:5000/v3" -msgstr "" +msgstr "如: http://openstack.example.com:5000/v3" #: xpack/plugins/cloud/serializers/account_attrs.py:110 msgid "User domain" -msgstr "" +msgstr "用户域" #: xpack/plugins/cloud/serializers/account_attrs.py:117 msgid "Cert File" -msgstr "" +msgstr "证书文件" #: xpack/plugins/cloud/serializers/account_attrs.py:118 msgid "Key File" -msgstr "" +msgstr "秘钥文件" #: xpack/plugins/cloud/serializers/account_attrs.py:134 msgid "Service account key" -msgstr "" +msgstr "服务账号密钥" #: xpack/plugins/cloud/serializers/account_attrs.py:135 msgid "The file is in JSON format" -msgstr "" +msgstr "JSON 格式的文件" #: xpack/plugins/cloud/serializers/account_attrs.py:148 msgid "IP address invalid `{}`, {}" -msgstr "" +msgstr "IP 地址无效: `{}`, {}" #: xpack/plugins/cloud/serializers/account_attrs.py:154 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" -msgstr "" +msgstr "格式为逗号分隔的字符串,如:192.168.1.0/24,10.0.0.0-10.0.0.255" #: xpack/plugins/cloud/serializers/account_attrs.py:158 msgid "" @@ -6863,22 +7059,24 @@ msgid "" "synchronization task is executed, only the valid IP address will be " "synchronized.
If the port is 0, all IP addresses are valid." msgstr "" +"端口用来检测 IP 地址的有效性,在同步任务执行时,只会同步有效的 IP 地址。
" +"如果端口为 0,则表示所有 IP 地址均有效。" #: xpack/plugins/cloud/serializers/account_attrs.py:166 msgid "Hostname prefix" -msgstr "" +msgstr "主机名前缀" #: xpack/plugins/cloud/serializers/account_attrs.py:169 msgid "IP segment" -msgstr "" +msgstr "IP 网段" #: xpack/plugins/cloud/serializers/account_attrs.py:173 msgid "Test port" -msgstr "" +msgstr "测试端口" #: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "Test timeout" -msgstr "" +msgstr "测试超时时间" #: xpack/plugins/cloud/serializers/task.py:29 msgid "" @@ -6888,108 +7086,552 @@ msgid "" "all instances and randomly match IP addresses.
Format for comma-" "delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" msgstr "" +"只有匹配到 IP 段的实例会被同步。
如果实例包含多个 IP 地址,那么第一个匹配" +"到的 IP 地址将被用作创建的资产的 IP。
默认值 * 表示同步所有实例和随机匹配 " +"IP 地址。
格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20" #: xpack/plugins/cloud/serializers/task.py:36 msgid "History count" -msgstr "" +msgstr "执行次数" #: xpack/plugins/cloud/serializers/task.py:37 msgid "Instance count" -msgstr "" +msgstr "实例个数" #: xpack/plugins/cloud/serializers/task.py:70 msgid "Linux admin user" -msgstr "" +msgstr "Linux 管理员" #: xpack/plugins/cloud/serializers/task.py:75 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" -msgstr "" +msgstr "定时执行" #: xpack/plugins/cloud/utils.py:69 msgid "Account unavailable" -msgstr "" +msgstr "账号无效" #: xpack/plugins/gathered_user/meta.py:11 msgid "Gathered user" -msgstr "" +msgstr "收集用户" #: xpack/plugins/gathered_user/models.py:34 msgid "Gather user task" -msgstr "" +msgstr "收集用户任务" #: xpack/plugins/gathered_user/models.py:80 msgid "gather user task execution" -msgstr "" +msgstr "收集用户执行" #: xpack/plugins/gathered_user/models.py:86 msgid "Assets is empty, please change nodes" -msgstr "" +msgstr "资产为空,请更改节点" #: xpack/plugins/gathered_user/serializers.py:21 msgid "Executed times" -msgstr "" +msgstr "执行次数" #: xpack/plugins/interface/api.py:52 msgid "Restore default successfully." -msgstr "" +msgstr "恢复默认成功!" #: xpack/plugins/interface/meta.py:10 msgid "Interface settings" -msgstr "" +msgstr "界面设置" #: xpack/plugins/interface/models.py:22 msgid "Title of login page" -msgstr "" +msgstr "登录页面标题" #: xpack/plugins/interface/models.py:26 msgid "Image of login page" -msgstr "" +msgstr "登录页面图片" #: xpack/plugins/interface/models.py:30 msgid "Website icon" -msgstr "" +msgstr "网站图标" #: xpack/plugins/interface/models.py:34 msgid "Logo of management page" -msgstr "" +msgstr "管理页面logo" #: xpack/plugins/interface/models.py:38 msgid "Logo of logout page" -msgstr "" +msgstr "退出页面logo" #: xpack/plugins/interface/models.py:40 msgid "Theme" -msgstr "" +msgstr "主题" #: xpack/plugins/interface/models.py:43 xpack/plugins/interface/models.py:84 msgid "Interface setting" -msgstr "" +msgstr "界面设置" #: xpack/plugins/license/api.py:50 msgid "License import successfully" -msgstr "" +msgstr "许可证导入成功" #: xpack/plugins/license/api.py:51 msgid "License is invalid" -msgstr "" +msgstr "无效的许可证" #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:127 msgid "License" -msgstr "" +msgstr "许可证" #: xpack/plugins/license/models.py:71 msgid "Standard edition" -msgstr "" +msgstr "标准版" #: xpack/plugins/license/models.py:73 msgid "Enterprise edition" -msgstr "" +msgstr "企业版" #: xpack/plugins/license/models.py:75 msgid "Ultimate edition" -msgstr "" +msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" -msgstr "" +msgstr "社区版" + +#~ msgid "System User" +#~ msgstr "系统用户" + +#~ msgid "" +#~ "Format for comma-delimited string, with * indicating a match all. " +#~ "Protocol options: {}" +#~ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" + +#~ msgid "Unsupported protocols: {}" +#~ msgstr "不支持的协议: {}" + +#~ msgid "Remote app" +#~ msgstr "远程应用" + +#~ msgid "Custom" +#~ msgstr "自定义" + +#~ msgid "Can view application account secret" +#~ msgstr "可以查看应用账号密码" + +#~ msgid "Can change application account secret" +#~ msgstr "可以查看应用账号密码" + +#~ msgid "Application user" +#~ msgstr "应用用户" + +#~ msgid "Type display" +#~ msgstr "类型名称" + +#~ msgid "Application display" +#~ msgstr "应用名称" + +#~ msgid "Cluster" +#~ msgstr "集群" + +#~ msgid "CA certificate" +#~ msgstr "CA 证书" + +#~ msgid "Client certificate file" +#~ msgstr "客户端证书" + +#~ msgid "Certificate key file" +#~ msgstr "证书秘钥" + +#~ msgid "Asset Info" +#~ msgstr "资产信息" + +#~ msgid "Application path" +#~ msgstr "应用路径" + +#~ msgid "Target URL" +#~ msgstr "目标URL" + +#~ msgid "Chrome username" +#~ msgstr "Chrome 用户名" + +#~ msgid "Chrome password" +#~ msgstr "Chrome 密码" + +#~ msgid "Operating parameter" +#~ msgstr "运行参数" + +#~ msgid "Target url" +#~ msgstr "目标URL" + +#~ msgid "Mysql workbench username" +#~ msgstr "Mysql 工作台 用户名" + +#~ msgid "Mysql workbench password" +#~ msgstr "Mysql 工作台 密码" + +#~ msgid "Vmware username" +#~ msgstr "Vmware 用户名" + +#~ msgid "Vmware password" +#~ msgstr "Vmware 密码" + +#~ msgid "Base" +#~ msgstr "基础" + +#~ msgid "Can test asset account connectivity" +#~ msgstr "可以测试资产账号连接性" + +#~ msgid "Bandwidth" +#~ msgstr "带宽" + +#~ msgid "Contact" +#~ msgstr "联系人" + +#~ msgid "Intranet" +#~ msgstr "内网" + +#~ msgid "Extranet" +#~ msgstr "外网" + +#~ msgid "Operator" +#~ msgstr "运营商" + +#~ msgid "Default Cluster" +#~ msgstr "默认Cluster" + +#~ msgid "Test gateway" +#~ msgstr "测试网关" + +#~ msgid "User groups" +#~ msgstr "用户组" + +#~ msgid "System user display" +#~ msgstr "系统用户名称" + +#~ msgid "Protocol format should {}/{}" +#~ msgstr "协议格式 {}/{}" + +#~ msgid "Nodes name" +#~ msgstr "节点名称" + +#~ msgid "Labels name" +#~ msgstr "标签名称" + +#~ msgid "Hardware info" +#~ msgstr "硬件信息" + +#~ msgid "Admin user display" +#~ msgstr "特权用户名称" + +#~ msgid "CPU info" +#~ msgstr "CPU信息" + +#~ msgid "Action display" +#~ msgstr "动作名称" + +#~ msgid "Applications amount" +#~ msgstr "应用数量" + +#~ msgid "Gateways count" +#~ msgstr "网关数量" + +#~ msgid "SSH key fingerprint" +#~ msgstr "密钥指纹" + +#~ msgid "Apps amount" +#~ msgstr "应用数量" + +#~ msgid "Nodes amount" +#~ msgstr "节点数量" + +#~ msgid "Login mode display" +#~ msgstr "认证方式名称" + +#~ msgid "Ad domain" +#~ msgstr "Ad 网域" + +#~ msgid "Is asset protocol" +#~ msgstr "资产协议" + +#~ msgid "Only ssh and automatic login system users are supported" +#~ msgstr "仅支持ssh协议和自动登录的系统用户" + +#~ msgid "Username same with user with protocol {} only allow 1" +#~ msgstr "用户名和用户相同的一种协议只允许存在一个" + +#~ msgid "* Automatic login mode must fill in the username." +#~ msgstr "自动登录模式,必须填写用户名" + +#~ msgid "Path should starts with /" +#~ msgstr "路径应该以 / 开头" + +#~ msgid "Password or private key required" +#~ msgstr "密码或密钥密码需要一个" + +#~ msgid "Only ssh protocol system users are allowed" +#~ msgstr "仅允许ssh协议的系统用户" + +#~ msgid "The protocol must be consistent with the current user: {}" +#~ msgstr "协议必须和当前用户保持一致: {}" + +#~ msgid "Only system users with automatic login are allowed" +#~ msgstr "仅允许自动登录的系统用户" + +#~ msgid "System user name" +#~ msgstr "系统用户名称" + +#~ msgid "Asset hostname" +#~ msgstr "资产主机名" + +#~ msgid "The asset {} system platform {} does not support run Ansible tasks" +#~ msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" + +#~ msgid "Test assets connectivity: " +#~ msgstr "测试资产可连接性: " + +#~ msgid "Unreachable" +#~ msgstr "不可达" + +#~ msgid "Reachable" +#~ msgstr "可连接" + +#~ msgid "Get asset info failed: {}" +#~ msgstr "获取资产信息失败:{}" + +#~ msgid "Update asset hardware info: " +#~ msgstr "更新资产硬件信息: " + +#~ msgid "System user is dynamic: {}" +#~ msgstr "系统用户是动态的: {}" + +#~ msgid "Start push system user for platform: [{}]" +#~ msgstr "推送系统用户到平台: [{}]" + +#~ msgid "Hosts count: {}" +#~ msgstr "主机数量: {}" + +#~ msgid "Push system users to asset: " +#~ msgstr "推送系统用户到入资产: " + +#~ msgid "Dynamic system user not support test" +#~ msgstr "动态系统用户不支持测试" + +#~ msgid "Start test system user connectivity for platform: [{}]" +#~ msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" + +#~ msgid "Test system user connectivity: " +#~ msgstr "测试系统用户可连接性: " + +#~ msgid "Test system user connectivity period: " +#~ msgstr "定期测试系统用户可连接性: " + +#~ msgid "Operate display" +#~ msgstr "操作名称" + +#~ msgid "Status display" +#~ msgstr "状态名称" + +#~ msgid "MFA display" +#~ msgstr "MFA名称" + +#~ msgid "Hosts display" +#~ msgstr "主机名称" + +#~ msgid "Run as" +#~ msgstr "运行用户" + +#~ msgid "Run as display" +#~ msgstr "运行用户名称" + +#~ msgid "User not exists" +#~ msgstr "用户不存在" + +#~ msgid "System user not exists" +#~ msgstr "系统用户不存在" + +#~ msgid "Asset not exists" +#~ msgstr "资产不存在" + +#~ msgid "User has no permission to access asset or permission expired" +#~ msgstr "用户没有权限访问资产或权限已过期" + +#~ msgid "User has no permission to access application or permission expired" +#~ msgstr "用户没有权限访问应用或权限已过期" + +#~ msgid "Asset or application required" +#~ msgstr "资产或应用必填" + +#~ msgid "Not has host {} permission" +#~ msgstr "没有该主机 {} 权限" + +#~ msgid "" +#~ "eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " +#~ "crontab expressions (Online tools)
Note: If both Regularly " +#~ "perform and Cycle perform are set, give priority to Regularly perform" +#~ msgstr "" +#~ "eg:每周日 03:05 执行 <5 3 * * 0>
提示: 使用5位 Linux crontab 表达" +#~ "式 <分 时 日 月 星期> (在线工具
注意: 如果同时设置了定期执行和周期执" +#~ "行,优先使用定期执行" + +#~ msgid "Unit: hour" +#~ msgstr "单位: 时" + +#~ msgid "Callback" +#~ msgstr "回调" + +#~ msgid "Can view task monitor" +#~ msgstr "可以查看任务监控" + +#~ msgid "Tasks" +#~ msgstr "任务" + +#~ msgid "Options" +#~ msgstr "选项" + +#~ msgid "Run as admin" +#~ msgstr "再次执行" + +#~ msgid "Become" +#~ msgstr "Become" + +#~ msgid "Create by" +#~ msgstr "创建者" + +#~ msgid "AdHoc" +#~ msgstr "任务各版本" + +#~ msgid "Task display" +#~ msgstr "任务名称" + +#~ msgid "Host amount" +#~ msgstr "主机数量" + +#~ msgid "Start time" +#~ msgstr "开始时间" + +#~ msgid "End time" +#~ msgstr "完成时间" + +#~ msgid "Adhoc raw result" +#~ msgstr "结果" + +#~ msgid "Adhoc result summary" +#~ msgstr "汇总" + +#~ msgid "AdHoc execution" +#~ msgstr "任务执行" + +#~ msgid "Task start" +#~ msgstr "任务开始" + +#~ msgid "Command `{}` is forbidden ........" +#~ msgstr "命令 `{}` 不允许被执行 ......." + +#~ msgid "Task end" +#~ msgstr "任务结束" + +#~ msgid "Clean task history period" +#~ msgstr "定期清除任务历史" + +#~ msgid "The administrator is modifying permissions. Please wait" +#~ msgstr "管理员正在修改授权,请稍等" + +#~ msgid "The authorization cannot be revoked for the time being" +#~ msgstr "该授权暂时不能撤销" + +#~ msgid "Application permission" +#~ msgstr "应用授权" + +#~ msgid "Permed application" +#~ msgstr "授权的应用" + +#~ msgid "Can view my apps" +#~ msgstr "可以查看我的应用" + +#~ msgid "Can view user apps" +#~ msgstr "可以查看用户授权的应用" + +#~ msgid "Can view usergroup apps" +#~ msgstr "可以查看用户组授权的应用" + +#~ msgid "Upload file" +#~ msgstr "上传文件" + +#~ msgid "Download file" +#~ msgstr "下载文件" + +#~ msgid "Upload download" +#~ msgstr "上传下载" + +#~ msgid "Clipboard paste" +#~ msgstr "剪贴板粘贴" + +#~ msgid "Clipboard copy paste" +#~ msgstr "剪贴板复制粘贴" + +#~ msgid "Your permed applications is about to expire" +#~ msgstr "你授权的应用即将过期" + +#~ msgid "permed applications" +#~ msgstr "授权的应用" + +#~ msgid "Application permissions is about to expire" +#~ msgstr "应用授权规则即将过期" + +#~ msgid "application permissions of organization {}" +#~ msgstr "组织 ({}) 的应用授权" + +#~ msgid "User groups amount" +#~ msgstr "用户组数量" + +#~ msgid "System users amount" +#~ msgstr "系统用户数量" + +#~ msgid "" +#~ "The application list contains applications that are different from the " +#~ "permission type. ({})" +#~ msgstr "应用列表中包含与授权类型不同的应用。({})" + +#~ msgid "Users display" +#~ msgstr "用户名称" + +#~ msgid "User groups display" +#~ msgstr "用户组名称" + +#~ msgid "Assets display" +#~ msgstr "资产名称" + +#~ msgid "Nodes display" +#~ msgstr "节点名称" + +#~ msgid "System users display" +#~ msgstr "系统用户名称" + +#~ msgid "My applications" +#~ msgstr "我的应用" + +#~ msgid "Empty" +#~ msgstr "空" + +#~ msgid "System user ID" +#~ msgstr "系统用户 ID" + +#~ msgid "Apply for application" +#~ msgstr "申请应用" + +#~ msgid "" +#~ "Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " +#~ "processor: {}, ticket ID: {}" +#~ msgstr "" +#~ "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" + +#~ msgid "Applied login IP" +#~ msgstr "申请登录的IP" + +#~ msgid "Applied login city" +#~ msgstr "申请登录的城市" + +#~ msgid "Applied login datetime" +#~ msgstr "申请登录的日期" + +#~ msgid "Login system user" +#~ msgstr "登录系统用户" From 6a1c5aba12e3bd5768ffcb109dc86aaeedaef58f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Dec 2022 17:24:30 +0800 Subject: [PATCH 008/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0095_auto_20220407_1726.py | 3 +- apps/assets/models/platform.py | 5 +- apps/assets/serializers/platform.py | 3 +- apps/locale/ja/LC_MESSAGES/django.po | 3 - apps/locale/zh/LC_MESSAGES/django.po | 363 +++++++----------- apps/orgs/mixins/models.py | 8 +- apps/orgs/models.py | 4 +- apps/orgs/signal_handlers/common.py | 1 + 8 files changed, 140 insertions(+), 250 deletions(-) diff --git a/apps/assets/migrations/0095_auto_20220407_1726.py b/apps/assets/migrations/0095_auto_20220407_1726.py index c11517a35..9d9baf42c 100644 --- a/apps/assets/migrations/0095_auto_20220407_1726.py +++ b/apps/assets/migrations/0095_auto_20220407_1726.py @@ -12,7 +12,6 @@ def migrate_platform_type_to_lower(apps, *args): class Migration(migrations.Migration): - dependencies = [ ('assets', '0094_auto_20220402_1736'), ] @@ -51,7 +50,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='su_method', - field=models.CharField(blank=True, max_length=32, null=True, verbose_name='SU method'), + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Su method'), ), migrations.RunPython(migrate_platform_type_to_lower) ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 64aeb2da3..a4e4ac26b 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -2,9 +2,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from assets.const import AllTypes -from common.db.fields import JsonDictTextField - from assets.const import Protocol +from common.db.fields import JsonDictTextField __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] @@ -83,7 +82,7 @@ class Platform(models.Model): protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols 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")) + 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")) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index ccb536bb2..6f7458543 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -25,7 +25,7 @@ class ProtocolSettingSerializer(serializers.Serializer): sftp_home = serializers.CharField(default="/tmp", label=_("SFTP home")) # HTTP - auto_fill = serializers.BooleanField(default=False, label=_("Auto fill")) + autofile = serializers.BooleanField(default=False, label=_("Autofill")) username_selector = serializers.CharField( default="", allow_blank=True, label=_("Username selector") ) @@ -38,7 +38,6 @@ class ProtocolSettingSerializer(serializers.Serializer): class PlatformAutomationSerializer(serializers.ModelSerializer): - class Meta: model = PlatformAutomation fields = [ diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index d5e72d0e0..ca196b2f7 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -656,18 +656,15 @@ msgid "All" msgstr "すべて" #: assets/models/account.py:46 -#, fuzzy msgid "Manual input" msgstr "手動入力" #: assets/models/account.py:47 -#, fuzzy msgid "Dynamic user" msgstr "動的コード" #: assets/models/account.py:55 #: authentication/serializers/connect_token_secret.py:47 -#, fuzzy msgid "Su from" msgstr "から切り替え" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index c57c03c00..524d6d076 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -651,18 +651,15 @@ msgid "All" msgstr "全部" #: assets/models/account.py:46 -#, fuzzy msgid "Manual input" msgstr "手动输入" #: assets/models/account.py:47 -#, fuzzy msgid "Dynamic user" -msgstr "动态码" +msgstr "同名账号" #: assets/models/account.py:55 #: authentication/serializers/connect_token_secret.py:47 -#, fuzzy msgid "Su from" msgstr "切换自" @@ -688,12 +685,10 @@ msgid "Can view asset history account secret" msgstr "可以查看资产历史账号密码" #: assets/models/account.py:93 assets/serializers/account/account.py:15 -#, fuzzy msgid "Account template" -msgstr "账号名称" +msgstr "账号模版" #: assets/models/account.py:98 -#, fuzzy msgid "Can view asset account template secret" msgstr "可以查看资产账号密码" @@ -765,19 +760,16 @@ msgid "Use SSL" msgstr "使用 SSL" #: assets/models/asset/database.py:10 -#, fuzzy msgid "CA cert" -msgstr "SP 证书" +msgstr "CA 证书" #: assets/models/asset/database.py:11 -#, fuzzy msgid "Client cert" -msgstr "客户端密钥" +msgstr "客户端证书" #: assets/models/asset/database.py:12 -#, fuzzy msgid "Client key" -msgstr "客户端" +msgstr "客户端密钥" #: assets/models/asset/database.py:13 msgid "Allow invalid cert" @@ -798,23 +790,20 @@ msgid "Script" msgstr "" #: assets/models/asset/web.py:13 -#, fuzzy msgid "Autofill" -msgstr "自动" +msgstr "自动代填" #: assets/models/asset/web.py:14 assets/serializers/platform.py:30 -#, fuzzy msgid "Username selector" -msgstr "用户名属性" +msgstr "用户名选择器" #: assets/models/asset/web.py:15 assets/serializers/platform.py:33 -#, fuzzy msgid "Password selector" -msgstr "密码规则" +msgstr "密码选择器" #: assets/models/asset/web.py:16 assets/serializers/platform.py:36 msgid "Submit selector" -msgstr "" +msgstr "确认按钮选择器" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:43 #: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 @@ -831,9 +820,8 @@ msgid "Assets" msgstr "资产" #: assets/models/automations/base.py:82 assets/models/automations/base.py:89 -#, fuzzy msgid "Automation task" -msgstr "托管密码" +msgstr "自动化任务" #: assets/models/automations/base.py:91 audits/models.py:129 #: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 @@ -865,7 +853,6 @@ msgstr "结束日期" #: assets/models/automations/base.py:96 #: assets/serializers/automations/base.py:39 -#, fuzzy msgid "Automation snapshot" msgstr "工单快照" @@ -879,21 +866,18 @@ msgstr "触发模式" #: assets/models/automations/base.py:104 #: assets/serializers/automations/change_secret.py:103 -#, fuzzy msgid "Automation task execution" -msgstr "同步实例任务执行" +msgstr "自动化任务执行历史" #: assets/models/automations/change_secret.py:15 assets/models/base.py:67 #: assets/serializers/account/account.py:97 assets/serializers/base.py:13 -#, fuzzy msgid "Secret type" -msgstr "Secret key" +msgstr "密问类型" #: assets/models/automations/change_secret.py:19 #: assets/serializers/automations/change_secret.py:25 -#, fuzzy msgid "Secret strategy" -msgstr "Secret key" +msgstr "密文策略" #: assets/models/automations/change_secret.py:21 #: assets/models/automations/change_secret.py:57 assets/models/base.py:69 @@ -909,7 +893,6 @@ msgid "Password rules" msgstr "密码规则" #: assets/models/automations/change_secret.py:25 -#, fuzzy msgid "SSH key change strategy" msgstr "SSH 密钥策略" @@ -923,59 +906,49 @@ msgid "Recipient" msgstr "收件人" #: assets/models/automations/change_secret.py:34 -#, fuzzy msgid "Change secret automation" -msgstr "安全设置" +msgstr "自动化改密" #: assets/models/automations/change_secret.py:56 -#, fuzzy msgid "Old secret" -msgstr "OTP 秘钥" +msgstr "原密码" #: assets/models/automations/change_secret.py:58 -#, fuzzy msgid "Date started" msgstr "开始日期" #: assets/models/automations/change_secret.py:61 common/const/choices.py:20 -#, fuzzy msgid "Error" -msgstr "企业微信错误" +msgstr "错误" #: assets/models/automations/change_secret.py:64 -#, fuzzy msgid "Change secret record" -msgstr "更改密码" +msgstr "改密记录" #: assets/models/automations/discovery_account.py:8 msgid "Discovery account automation" -msgstr "" +msgstr "账号发现" #: assets/models/automations/gather_accounts.py:15 #: assets/tasks/gather_accounts.py:28 -#, fuzzy msgid "Gather asset accounts" msgstr "收集账号" #: assets/models/automations/gather_facts.py:15 -#, fuzzy msgid "Gather asset facts" -msgstr "收集资产上的用户" +msgstr "收集资产信息" #: assets/models/automations/ping.py:15 -#, fuzzy msgid "Ping asset" -msgstr "登录资产" +msgstr "测试资产" #: assets/models/automations/push_account.py:16 -#, fuzzy msgid "Push asset account" -msgstr "服务账号" +msgstr "账号推送" #: assets/models/automations/verify_account.py:15 -#, fuzzy msgid "Verify asset account" -msgstr "验证密码/密钥" +msgstr "账号验证" #: assets/models/backup.py:37 assets/models/backup.py:95 msgid "Account backup plan" @@ -1057,9 +1030,8 @@ msgid "Command filter rule" msgstr "命令过滤规则" #: assets/models/gateway.py:61 authentication/models/connection_token.py:101 -#, fuzzy msgid "No account" -msgstr "账号" +msgstr "没有账号" #: assets/models/gateway.py:83 #, fuzzy, python-brace-format @@ -1151,9 +1123,8 @@ msgid "Can match node" msgstr "可以匹配节点" #: assets/models/platform.py:20 -#, fuzzy msgid "Required" -msgstr "需要 MFA 认证" +msgstr "必须的" #: assets/models/platform.py:23 settings/serializers/settings.py:61 #: users/templates/users/reset_password.html:29 @@ -1167,55 +1138,47 @@ msgstr "启用" #: assets/models/platform.py:43 msgid "Ansible config" -msgstr "" +msgstr "Ansible 配置" #: assets/models/platform.py:44 -#, fuzzy msgid "Ping enabled" -msgstr "MFA 已启用" +msgstr "启用资产探活" #: assets/models/platform.py:45 msgid "Ping method" -msgstr "" +msgstr "资产探活方式" #: assets/models/platform.py:46 assets/models/platform.py:56 -#, fuzzy msgid "Gather facts enabled" -msgstr "收集资产上的用户" +msgstr "收集资产信息" #: assets/models/platform.py:47 assets/models/platform.py:58 -#, fuzzy msgid "Gather facts method" -msgstr "收集资产上的用户" +msgstr "收集信息方式" #: assets/models/platform.py:48 -#, fuzzy msgid "Push account enabled" -msgstr "MFA 多因子认证没有开启" +msgstr "启用账号推送" #: assets/models/platform.py:49 msgid "Push account method" -msgstr "" +msgstr "账号推送方式" #: assets/models/platform.py:50 -#, fuzzy msgid "Change password enabled" -msgstr "更改密码" +msgstr "开启账号改密" #: assets/models/platform.py:52 -#, fuzzy msgid "Change password method" -msgstr "更改密码" +msgstr "更改密码方式" #: assets/models/platform.py:53 -#, fuzzy msgid "Verify account enabled" -msgstr "服务账号密钥" +msgstr "开启账号验证" #: assets/models/platform.py:55 -#, fuzzy msgid "Verify account method" -msgstr "验证密码/密钥" +msgstr "账号验证方式" #: assets/models/platform.py:75 tickets/models/ticket/general.py:299 msgid "Meta" @@ -1230,28 +1193,24 @@ msgid "Charset" msgstr "编码" #: assets/models/platform.py:82 -#, fuzzy msgid "Domain enabled" -msgstr "网域名称" +msgstr "启用网域" #: assets/models/platform.py:83 -#, fuzzy msgid "Protocols enabled" -msgstr "协议组" +msgstr "启用协议" #: assets/models/platform.py:85 -#, fuzzy msgid "Su enabled" -msgstr "MFA 已启用" +msgstr "启用账号切换" #: assets/models/platform.py:86 -msgid "SU method" -msgstr "" +msgid "Su method" +msgstr "账号切换方式" #: assets/models/platform.py:88 assets/serializers/platform.py:104 -#, fuzzy msgid "Automation" -msgstr "托管密码" +msgstr "自动化" #: assets/models/utils.py:19 #, python-format @@ -1300,16 +1259,15 @@ msgstr "" #: assets/serializers/account/account.py:18 msgid "Push now" -msgstr "" +msgstr "立即推送" #: assets/serializers/account/account.py:20 -#, fuzzy msgid "Has secret" -msgstr "密钥" +msgstr "已托管密码" #: assets/serializers/account/account.py:27 msgid "Account template not found" -msgstr "" +msgstr "账号模版未找到" #: assets/serializers/account/backup.py:29 #: assets/serializers/automations/base.py:34 ops/mixin.py:22 ops/mixin.py:102 @@ -1337,14 +1295,12 @@ msgid "Address" msgstr "地址" #: assets/serializers/asset/common.py:156 -#, fuzzy msgid "Platform not exist" -msgstr "应用不存在" +msgstr "平台不存在" #: assets/serializers/asset/common.py:172 -#, fuzzy msgid "Protocol is required: {}" -msgstr "协议重复: {}" +msgstr "协议是必填的: {}" #: assets/serializers/asset/host.py:12 msgid "Vendor" @@ -1431,7 +1387,6 @@ msgid "Success" msgstr "成功" #: assets/serializers/automations/gather_accounts.py:23 -#, fuzzy msgid "Executed amount" msgstr "执行次数" @@ -1444,7 +1399,6 @@ msgid "Constraints" msgstr "" #: assets/serializers/cagegory.py:15 -#, fuzzy msgid "Types" msgstr "类型" @@ -1477,23 +1431,17 @@ msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" #: assets/serializers/platform.py:24 -#, fuzzy msgid "SFTP enabled" -msgstr "MFA 已启用" +msgstr "SFTP 已启用" #: assets/serializers/platform.py:25 -#, fuzzy msgid "SFTP home" msgstr "SFTP根路径" -#: assets/serializers/platform.py:28 -#, fuzzy -msgid "Auto fill" -msgstr "自动" #: assets/serializers/platform.py:79 msgid "Primary" -msgstr "" +msgstr "主要的" #: assets/serializers/utils.py:13 msgid "Password can not contains `{{` " @@ -1512,36 +1460,32 @@ msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" #: assets/tasks/automation.py:11 -#, fuzzy msgid "Execute automation" -msgstr "执行批量命令" +msgstr "执行自动化任务" #: assets/tasks/backup.py:13 -#, fuzzy msgid "Execute account backup plan" -msgstr "账号备份计划" +msgstr "执行账号备份计划" #: assets/tasks/gather_accounts.py:31 -#, fuzzy msgid "Gather assets accounts" -msgstr "收集资产上的用户" +msgstr "收集资产上的账号" #: assets/tasks/gather_facts.py:26 msgid "Update some assets hardware info. " msgstr "更新资产硬件信息. " #: assets/tasks/gather_facts.py:44 -#, fuzzy msgid "Manually update the hardware information of assets" -msgstr "更新节点资产硬件信息: " +msgstr "手动更新资产信息" #: assets/tasks/gather_facts.py:49 msgid "Update assets hardware info: " -msgstr "更新资产硬件信息: " +msgstr "更新资产硬件信息" #: assets/tasks/gather_facts.py:53 msgid "Manually update the hardware information of assets under a node" -msgstr "" +msgstr "手动更新节点下资产信息" #: assets/tasks/gather_facts.py:59 msgid "Update node asset hardware information: " @@ -1549,7 +1493,7 @@ msgstr "更新节点资产硬件信息: " #: assets/tasks/nodes_amount.py:16 msgid "Check the amount of assets under the node" -msgstr "" +msgstr "检查节点下资产数量" #: assets/tasks/nodes_amount.py:28 msgid "" @@ -1558,31 +1502,27 @@ msgstr "自检程序已经在运行,不能重复启动" #: assets/tasks/nodes_amount.py:34 msgid "Periodic check the amount of assets under the node" -msgstr "" +msgstr "周期性检查节点下资产数量" #: assets/tasks/ping.py:21 assets/tasks/ping.py:39 -#, fuzzy msgid "Test assets connectivity " -msgstr "测试资产可连接性. " +msgstr "测试资产可连接性" #: assets/tasks/ping.py:33 -#, fuzzy msgid "Manually test the connectivity of a asset" -msgstr "可以测试资产连接性" +msgstr "手动测试资产连接性" #: assets/tasks/ping.py:43 msgid "Manually test the connectivity of assets under a node" -msgstr "" +msgstr "手动测试节点下资产连接性" #: assets/tasks/ping.py:49 -#, fuzzy msgid "Test if the assets under the node are connectable " -msgstr "测试节点下资产是否可连接: " +msgstr "测试节点下资产是否可连接" #: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 -#, fuzzy msgid "Push accounts to assets" -msgstr "推送系统用户到入资产: " +msgstr "推送账号到资产" #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" @@ -1605,9 +1545,8 @@ msgid "Verify asset account availability" msgstr "" #: assets/tasks/verify_account.py:37 -#, fuzzy msgid "Verify accounts connectivity" -msgstr "测试账号可连接性: " +msgstr "测试账号可连接性" #: audits/apps.py:9 msgid "Audits" @@ -1975,11 +1914,10 @@ msgid "" msgstr "账号已被锁定(请联系管理员解锁或{}分钟后重试)" #: authentication/errors/const.py:51 -#, fuzzy msgid "" "The address has been locked (please contact admin to unlock it or try again " "after {} minutes)" -msgstr "IP 已被锁定(请联系管理员解锁或{}分钟后重试)" +msgstr "IP 已被锁定(请联系管理员解锁或 {} 分钟后重试)" #: authentication/errors/const.py:59 #, python-brace-format @@ -2174,21 +2112,18 @@ msgid "Account name" msgstr "账号名称" #: authentication/models/connection_token.py:32 -#, fuzzy msgid "Input username" msgstr "自定义用户名" #: authentication/models/connection_token.py:33 -#, fuzzy msgid "Input secret" -msgstr "客户端密钥" +msgstr "自定义密码" #: authentication/models/connection_token.py:35 #: authentication/serializers/connect_token_secret.py:110 #: perms/models/perm_token.py:17 -#, fuzzy msgid "Connect method" -msgstr "连接超时时间" +msgstr "连接方式" #: authentication/models/connection_token.py:36 #: rbac/serializers/rolebinding.py:21 @@ -2220,12 +2155,11 @@ msgstr "连接令牌过期: {}" #: authentication/models/connection_token.py:94 msgid "No user or invalid user" -msgstr "" +msgstr "没有用户或用户失效" #: authentication/models/connection_token.py:98 -#, fuzzy msgid "No asset or inactive asset" -msgstr "资产未激活" +msgstr "没有资产或资产未激活" #: authentication/models/connection_token.py:173 msgid "Super connection token" @@ -2256,9 +2190,8 @@ msgid "binding reminder" msgstr "绑定提醒" #: authentication/serializers/connect_token_secret.py:109 -#, fuzzy msgid "Expired now" -msgstr "过期时间" +msgstr "立刻过期" #: authentication/serializers/connection_token.py:14 msgid "Expired time" @@ -2674,10 +2607,9 @@ msgstr "待定的" #: common/const/choices.py:17 msgid "Running" -msgstr "" +msgstr "运行中" #: common/const/choices.py:21 -#, fuzzy msgid "Canceled" msgstr "取消" @@ -2745,9 +2677,8 @@ msgid "Invalid data type, should be list" msgstr "" #: common/drf/fields.py:156 -#, fuzzy msgid "Invalid choice: {}" -msgstr "无效IP" +msgstr "无效选项: {}" #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" @@ -2762,9 +2693,8 @@ msgid "Children" msgstr "" #: common/drf/serializers/common.py:94 -#, fuzzy msgid "File" -msgstr "文件名" +msgstr "文件" #: common/exceptions.py:15 #, python-format @@ -2885,22 +2815,20 @@ msgid "Please wait {} seconds before sending" msgstr "请在 {} 秒后发送" #: common/tasks.py:13 -#, fuzzy msgid "Send email" -msgstr "发件人" +msgstr "发件邮件" #: common/tasks.py:40 msgid "Send email attachment" -msgstr "" +msgstr "发送邮件附件" #: common/utils/ip/geoip/utils.py:26 msgid "Invalid ip" msgstr "无效IP" #: common/utils/ip/utils.py:78 -#, fuzzy msgid "Invalid address" -msgstr "签名无效" +msgstr "无效地址" #: common/validators.py:14 msgid "Special char not allowed" @@ -2986,18 +2914,16 @@ msgid "Publish the station message" msgstr "" #: ops/ansible/inventory.py:75 -#, fuzzy msgid "No account available" -msgstr "账号无效" +msgstr "无可用账号" #: ops/ansible/inventory.py:178 -#, fuzzy msgid "Ansible disabled" -msgstr "用户已禁用" +msgstr "Ansible 已禁用" #: ops/ansible/inventory.py:194 msgid "Skip hosts below:" -msgstr "" +msgstr "跳过以下主机: " #: ops/api/celery.py:63 ops/api/celery.py:78 msgid "Waiting task start" @@ -3009,21 +2935,19 @@ msgstr "作业中心" #: ops/const.py:6 msgid "Push" -msgstr "" +msgstr "推送" #: ops/const.py:7 -#, fuzzy msgid "Verify" -msgstr "已校验" +msgstr "校验" #: ops/const.py:8 msgid "Collect" -msgstr "" +msgstr "收集" #: ops/const.py:9 -#, fuzzy msgid "Change password" -msgstr "更改密码" +msgstr "改密" #: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 msgid "Custom password" @@ -3031,7 +2955,7 @@ msgstr "自定义密码" #: ops/exception.py:6 msgid "no valid program entry found." -msgstr "" +msgstr "没有可用程序入口" #: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:73 msgid "Cycle perform" @@ -3059,7 +2983,6 @@ msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" #: ops/models/adhoc.py:18 ops/models/job.py:31 -#, fuzzy msgid "Powershell" msgstr "PowerShell" @@ -3069,7 +2992,7 @@ msgstr "模式" #: ops/models/adhoc.py:24 ops/models/job.py:38 msgid "Module" -msgstr "" +msgstr "模块" #: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 #: terminal/models/component/task.py:17 @@ -3083,19 +3006,16 @@ msgid "Creator" msgstr "创建者" #: ops/models/base.py:19 -#, fuzzy msgid "Account policy" -msgstr "账号密钥" +msgstr "账号策略" #: ops/models/base.py:20 -#, fuzzy msgid "Last execution" -msgstr "命令执行" +msgstr "最后执行" #: ops/models/base.py:22 -#, fuzzy msgid "Date last run" -msgstr "最后同步日期" +msgstr "最后运行日期" #: ops/models/base.py:51 ops/models/job.py:105 #: xpack/plugins/cloud/models.py:172 @@ -3104,7 +3024,7 @@ msgstr "结果" #: ops/models/base.py:52 ops/models/job.py:106 msgid "Summary" -msgstr "" +msgstr "汇总" #: ops/models/celery.py:55 terminal/models/component/task.py:18 msgid "Kwargs" @@ -3122,29 +3042,28 @@ msgid "Finished" msgstr "结束" #: ops/models/celery.py:58 -#, fuzzy msgid "Date published" -msgstr "结束日期" +msgstr "发布日期" #: ops/models/job.py:21 msgid "Adhoc" -msgstr "" +msgstr "命令" #: ops/models/job.py:22 ops/models/job.py:41 msgid "Playbook" -msgstr "" +msgstr "Playbook" #: ops/models/job.py:25 msgid "Privileged Only" -msgstr "" +msgstr "仅限特权账号" #: ops/models/job.py:26 msgid "Privileged First" -msgstr "" +msgstr "特权账号优先" #: ops/models/job.py:27 msgid "Skip" -msgstr "" +msgstr "跳过" #: ops/models/job.py:39 msgid "Chdir" @@ -3203,14 +3122,12 @@ msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" #: ops/serializers/job.py:10 -#, fuzzy msgid "Run after save" -msgstr "运行的系统用户" +msgstr "保存后执行" #: ops/serializers/job.py:11 -#, fuzzy msgid "Job type" -msgstr "文档类型" +msgstr "任务类型" #: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 #: terminal/models/component/task.py:26 @@ -3219,36 +3136,32 @@ msgid "Task" msgstr "任务" #: ops/tasks.py:28 -#, fuzzy msgid "Run ansible task" -msgstr "运行的资产" +msgstr "运行 Ansible 任务" #: ops/tasks.py:35 -#, fuzzy msgid "Run ansible task execution" -msgstr "同步实例任务执行" +msgstr "开始执行 Ansible 任务" #: ops/tasks.py:48 msgid "Periodic clear celery tasks" -msgstr "" +msgstr "周期清理不可用任务" #: ops/tasks.py:50 msgid "Clean celery log period" -msgstr "定期清除Celery日志" +msgstr "定期清除任务日志" #: ops/tasks.py:67 -#, fuzzy msgid "Clear celery periodic tasks" -msgstr "定期清除Celery日志" +msgstr "清理周期任务" #: ops/tasks.py:90 msgid "Create or update periodic tasks" -msgstr "" +msgstr "创建或更新周期任务" #: ops/tasks.py:98 -#, fuzzy msgid "Periodic check service performance" -msgstr "定时执行" +msgstr "周期检测服务性能" #: ops/templates/ops/celery_task_log.html:4 msgid "Task log" @@ -3288,9 +3201,8 @@ msgid "Org name" msgstr "组织名称" #: orgs/models.py:72 -#, fuzzy msgid "Builtin" -msgstr "内置" +msgstr "内置的" #: orgs/models.py:80 msgid "GLOBAL" @@ -3298,11 +3210,11 @@ msgstr "全局组织" #: orgs/models.py:82 msgid "DEFAULT" -msgstr "" +msgstr "默认组织" #: orgs/models.py:84 msgid "SYSTEM" -msgstr "" +msgstr "系统组织" #: orgs/models.py:90 msgid "Can view root org" @@ -3313,9 +3225,8 @@ msgid "Can view all joined org" msgstr "可以查看所有加入的组织" #: orgs/tasks.py:9 -#, fuzzy msgid "Refresh organization cache" -msgstr "全局组织名" +msgstr "刷新组织缓存" #: perms/apps.py:9 msgid "App permissions" @@ -3326,22 +3237,20 @@ msgid "Connect" msgstr "连接" #: perms/const.py:15 -#, fuzzy msgid "Copy" -msgstr "复制链接" +msgstr "复制" #: perms/const.py:16 msgid "Paste" -msgstr "" +msgstr "粘贴" #: perms/const.py:26 msgid "Transfer" -msgstr "" +msgstr "文件传输" #: perms/const.py:27 -#, fuzzy msgid "Clipboard" -msgstr "剪贴板复制" +msgstr "剪贴板" #: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 #: perms/serializers/permission.py:29 perms/serializers/permission.py:59 @@ -3383,13 +3292,12 @@ msgid "Can view usergroup assets" msgstr "可以查看用户组授权的资产" #: perms/models/perm_node.py:119 -#, fuzzy msgid "Permed account" -msgstr "收集账号" +msgstr "授权账号" #: perms/notifications.py:12 perms/notifications.py:44 msgid "today" -msgstr "今" +msgstr "今天" #: perms/notifications.py:15 msgid "You permed assets is about to expire" @@ -5075,7 +4983,6 @@ msgid "Session ID" msgstr "会话ID" #: terminal/backends/command/serializers.py:37 -#, fuzzy msgid "Account " msgstr "账号" @@ -5110,7 +5017,6 @@ msgstr "离线" #: terminal/const.py:81 terminal/const.py:82 terminal/const.py:83 #: terminal/const.py:84 terminal/const.py:85 -#, fuzzy msgid "DB Client" msgstr "客户端" @@ -5123,50 +5029,44 @@ msgid "Storage is invalid" msgstr "存储无效" #: terminal/models/applet/applet.py:23 -#, fuzzy msgid "Author" -msgstr "资产账号" +msgstr "作者" #: terminal/models/applet/applet.py:27 msgid "Tags" -msgstr "" +msgstr "标签" #: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 msgid "Hosts" msgstr "主机" #: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 -#, fuzzy msgid "Applet" -msgstr "申请资产" +msgstr "远程应用" #: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 -#, fuzzy msgid "Deploy options" -msgstr "其他方式登录" +msgstr "部署参数" #: terminal/models/applet/host.py:19 msgid "Inited" -msgstr "" +msgstr "已初始化" #: terminal/models/applet/host.py:20 -#, fuzzy msgid "Date inited" -msgstr "结束日期" +msgstr "初始化日期" #: terminal/models/applet/host.py:21 -#, fuzzy msgid "Date synced" msgstr "同步日期" #: terminal/models/applet/host.py:102 -#, fuzzy msgid "Hosting" -msgstr "主机" +msgstr "宿主机" #: terminal/models/applet/host.py:103 msgid "Initial" -msgstr "" +msgstr "初始化" #: terminal/models/component/endpoint.py:15 msgid "HTTPS Port" @@ -5363,45 +5263,40 @@ msgid "Batch danger command alert" msgstr "批量危险命令告警" #: terminal/serializers/applet.py:16 -#, fuzzy msgid "Published" -msgstr "SSH公钥" +msgstr "已发布" #: terminal/serializers/applet.py:17 -#, fuzzy msgid "Unpublished" -msgstr "结束" +msgstr "未发布" #: terminal/serializers/applet.py:18 -#, fuzzy msgid "Not match" -msgstr "没有匹配到用户" +msgstr "没有匹配的" #: terminal/serializers/applet.py:32 msgid "Icon" -msgstr "" +msgstr "图标" #: terminal/serializers/applet_host.py:21 -#, fuzzy msgid "Per Session" -msgstr "会话" +msgstr "每会话" #: terminal/serializers/applet_host.py:22 msgid "Per Device" -msgstr "" +msgstr "每设备" #: terminal/serializers/applet_host.py:28 -#, fuzzy msgid "RDS Licensing" -msgstr "许可证" +msgstr "RDS 许可证" #: terminal/serializers/applet_host.py:29 msgid "RDS License Server" -msgstr "" +msgstr "RDS 许可服务器" #: terminal/serializers/applet_host.py:30 msgid "RDS Licensing Mode" -msgstr "" +msgstr "RDS 授权模式" #: terminal/serializers/applet_host.py:32 msgid "RDS fSingleSessionPerUser" diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index 0795edc2e..d9eef1e2f 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- # +from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.core.exceptions import ValidationError -from common.utils import get_logger from common.db.models import JMSBaseModel +from common.utils import get_logger, lazyproperty +from ..models import Organization from ..utils import ( set_current_org, get_current_org, current_org, filter_org_queryset ) -from ..models import Organization logger = get_logger(__file__) @@ -75,7 +75,7 @@ class OrgModelMixin(models.Model): self.org_id = org.id return super().save(*args, **kwargs) - @property + @lazyproperty def org(self): return Organization.get_instance(self.org_id) diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 73dc3c6ad..89f50f4bb 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -3,8 +3,8 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils import lazyproperty, settings from common.tree import TreeNode +from common.utils import lazyproperty, settings class OrgRoleMixin: @@ -33,7 +33,6 @@ class OrgRoleMixin: def get_origin_role_members(self, role_name): from rbac.models import OrgRoleBinding - from users.models import User from rbac.builtin import BuiltinRole from .utils import tmp_to_org @@ -132,6 +131,7 @@ class Organization(OrgRoleMixin, models.Model): @classmethod def expire_orgs_mapping(cls): + print("Expire orgs mapping: ") cls.orgs_mapping = None def org_id(self): diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index 4935eeec9..802e9c299 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -56,6 +56,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs): def on_org_create_or_update(sender, instance, created=False, **kwargs): # 必须放到最开始, 因为下面调用Node.save方法时会获取当前组织的org_id(即instance.org_id), 如果不过期会找不到 expire_orgs_mapping_for_memory(instance.id) + old_org = get_current_org() set_current_org(instance) node_root = Node.org_root() From 6cda28c63d26ace1bfdd790c6eac17251da26f86 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 7 Dec 2022 18:38:03 +0800 Subject: [PATCH 009/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E8=A7=84=E5=88=99=20user-permission=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E7=9B=B8=E5=85=B3=E7=9A=84=20API;?= =?UTF-8?q?=20=E5=8C=85=E6=8B=AC=20assets,=20nodes,=20tree-asset,=20tree-n?= =?UTF-8?q?ode,=20tree-node-with-asset;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 7 +- apps/perms/api/user_group_permission.py | 8 +- apps/perms/api/user_permission/__init__.py | 2 +- apps/perms/api/user_permission/accounts.py | 3 +- apps/perms/api/user_permission/assets.py | 111 +++++------- apps/perms/api/user_permission/mixin.py | 51 +----- apps/perms/api/user_permission/nodes.py | 132 +++------------ .../api/user_permission/nodes_with_assets.py | 158 ------------------ .../api/user_permission/tree/__init__.py | 3 + apps/perms/api/user_permission/tree/asset.py | 48 ++++++ apps/perms/api/user_permission/tree/mixin.py | 17 ++ apps/perms/api/user_permission/tree/node.py | 39 +++++ .../user_permission/tree/node_with_asset.py | 125 ++++++++++++++ apps/perms/pagination.py | 4 +- apps/perms/serializers/user_permission.py | 4 +- apps/perms/urls/user_permission.py | 53 +++--- apps/perms/utils/permission.py | 2 + 17 files changed, 339 insertions(+), 428 deletions(-) delete mode 100644 apps/perms/api/user_permission/nodes_with_assets.py create mode 100644 apps/perms/api/user_permission/tree/__init__.py create mode 100644 apps/perms/api/user_permission/tree/asset.py create mode 100644 apps/perms/api/user_permission/tree/mixin.py create mode 100644 apps/perms/api/user_permission/tree/node.py create mode 100644 apps/perms/api/user_permission/tree/node_with_asset.py diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index df139a8c9..3b32ca748 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -30,16 +30,13 @@ __all__ = [ class AssetFilterSet(BaseFilterSet): type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact") - category = django_filters.CharFilter( - field_name="platform__category", lookup_expr="exact" - ) - hostname = django_filters.CharFilter(field_name="name", lookup_expr="exact") + category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact") class Meta: model = Asset fields = [ "id", "name", "address", "is_active", - "type", "category", "hostname" + "type", "category" ] diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 48f94f6f7..c9abeee6c 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -23,8 +23,8 @@ __all__ = [ class UserGroupGrantedAssetsApi(ListAPIView): - serializer_class = serializers.AssetGrantedSerializer - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields + serializer_class = serializers.AssetPermedSerializer + only_fields = serializers.AssetPermedSerializer.Meta.only_fields filterset_fields = ['name', 'address', 'id', 'comment'] search_fields = ['name', 'address', 'comment'] rbac_perms = { @@ -60,8 +60,8 @@ class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView): - serializer_class = serializers.AssetGrantedSerializer - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields + serializer_class = serializers.AssetPermedSerializer + only_fields = serializers.AssetPermedSerializer.Meta.only_fields filterset_fields = ['name', 'address', 'id', 'comment'] search_fields = ['name', 'address', 'comment'] rbac_perms = { diff --git a/apps/perms/api/user_permission/__init__.py b/apps/perms/api/user_permission/__init__.py index 55bc108b4..dc67ce7f2 100644 --- a/apps/perms/api/user_permission/__init__.py +++ b/apps/perms/api/user_permission/__init__.py @@ -2,5 +2,5 @@ # from .nodes import * from .assets import * -from .nodes_with_assets import * from .accounts import * +from .tree import * diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 96c09667f..bb94d12aa 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -25,6 +25,5 @@ class UserPermedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): return asset def get_queryset(self): - util = PermAccountUtil() - accounts = util.get_permed_accounts_for_user(self.user, self.asset) + accounts = PermAccountUtil().get_permed_accounts_for_user(self.user, self.asset) return accounts diff --git a/apps/perms/api/user_permission/assets.py b/apps/perms/api/user_permission/assets.py index d3ee274aa..e494b7a4a 100644 --- a/apps/perms/api/user_permission/assets.py +++ b/apps/perms/api/user_permission/assets.py @@ -1,109 +1,76 @@ -from django.conf import settings +import abc from rest_framework.generics import ListAPIView from assets.models import Asset, Node -from common.utils import get_logger +from assets.api.asset.asset import AssetFilterSet from perms import serializers -from perms.pagination import AllGrantedAssetPagination -from perms.pagination import NodeGrantedAssetPagination +from perms.pagination import AllPermedAssetPagination +from perms.pagination import NodePermedAssetPagination from perms.utils.user_permission import UserGrantedAssetsQueryUtils +from common.utils import get_logger, lazyproperty + from .mixin import ( - SelfOrPKUserMixin, RebuildTreeMixin, - PermedAssetSerializerMixin, AssetsTreeFormatMixin + SelfOrPKUserMixin ) + __all__ = [ + 'UserAllPermedAssetsApi', 'UserDirectPermedAssetsApi', 'UserFavoriteAssetsApi', - 'UserDirectPermedAssetsAsTreeApi', - 'UserUngroupAssetsAsTreeApi', - 'UserAllPermedAssetsApi', 'UserPermedNodeAssetsApi', ] logger = get_logger(__name__) -class UserDirectPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - """ 直接授权给用户的资产 """ - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields +class BaseUserPermedAssetsApi(SelfOrPKUserMixin, ListAPIView): + ordering = ('name',) + ordering_fields = ("name", "address") + search_fields = ('name', 'address', 'comment') + filterset_class = AssetFilterSet + serializer_class = serializers.AssetPermedSerializer + only_fields = serializers.AssetPermedSerializer.Meta.only_fields def get_queryset(self): if getattr(self, 'swagger_fake_view', False): return Asset.objects.none() - - assets = UserGrantedAssetsQueryUtils(self.user) \ - .get_direct_granted_assets() \ - .prefetch_related('platform') \ - .only(*self.only_fields) - return assets - - -class UserFavoriteAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - """ 用户收藏的授权资产 """ - - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() - - user = self.user - utils = UserGrantedAssetsQueryUtils(user) - assets = utils.get_favorite_assets() + assets = self.get_assets() assets = assets.prefetch_related('platform').only(*self.only_fields) return assets + @abc.abstractmethod + def get_assets(self): + return Asset.objects.none() -class UserDirectPermedAssetsAsTreeApi(RebuildTreeMixin, AssetsTreeFormatMixin, UserDirectPermedAssetsApi): - """ 用户直接授权的资产作为树 """ - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() - - assets = UserGrantedAssetsQueryUtils(self.user) \ - .get_direct_granted_assets() \ - .prefetch_related('platform') \ - .only(*self.only_fields) - return assets + @lazyproperty + def query_asset_util(self): + return UserGrantedAssetsQueryUtils(self.user) -class UserUngroupAssetsAsTreeApi(UserDirectPermedAssetsAsTreeApi): - """ 用户未分组节点下的资产作为树 """ +class UserAllPermedAssetsApi(BaseUserPermedAssetsApi): + pagination_class = AllPermedAssetPagination - def get_queryset(self): - queryset = super().get_queryset() - if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - queryset = queryset.none() - return queryset + def get_assets(self): + return self.query_asset_util.get_all_granted_assets() -class UserAllPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - pagination_class = AllGrantedAssetPagination - - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() - queryset = UserGrantedAssetsQueryUtils(self.user).get_all_granted_assets() - only_fields = [i for i in self.only_fields if i not in ['protocols']] - queryset = queryset.prefetch_related('platform', 'protocols').only(*only_fields) - return queryset +class UserDirectPermedAssetsApi(BaseUserPermedAssetsApi): + def get_assets(self): + return self.query_asset_util.get_direct_granted_assets() -class UserPermedNodeAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - pagination_class = NodeGrantedAssetPagination - kwargs: dict +class UserFavoriteAssetsApi(BaseUserPermedAssetsApi): + def get_assets(self): + return self.query_asset_util.get_favorite_assets() + + +class UserPermedNodeAssetsApi(BaseUserPermedAssetsApi): + pagination_class = NodePermedAssetPagination pagination_node: Node - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() + def get_assets(self): node_id = self.kwargs.get("node_id") - - node, assets = UserGrantedAssetsQueryUtils(self.user).get_node_all_assets(node_id) - assets = assets.prefetch_related('platform').only(*self.only_fields) + node, assets = self.query_asset_util.get_node_all_assets(node_id) self.pagination_node = node return assets diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 7be96caed..4f4d48181 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -3,27 +3,13 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext_lazy as _ from rest_framework.request import Request -from rest_framework.response import Response -from assets.api.asset.asset import AssetFilterSet -from assets.api.mixin import SerializeToTreeNodeMixin -from common.exceptions import JMSObjectDoesNotExist -from common.http import is_true -from common.utils import is_uuid -from perms import serializers -from perms.utils.user_permission import UserGrantedTreeRefreshController -from rbac.permissions import RBACPermission from users.models import User +from rbac.permissions import RBACPermission +from common.utils import is_uuid +from common.exceptions import JMSObjectDoesNotExist - -class RebuildTreeMixin: - user: User - - def get(self, request: Request, *args, **kwargs): - force = is_true(request.query_params.get('rebuild_tree')) - controller = UserGrantedTreeRefreshController(self.user) - controller.refresh_if_need(force) - return super().get(request, *args, **kwargs) +__all__ = ['SelfOrPKUserMixin'] class SelfOrPKUserMixin: @@ -72,32 +58,3 @@ class SelfOrPKUserMixin: def request_user_is_self(self): return self.kwargs.get('user') in ['my', 'self'] - -class PermedAssetSerializerMixin: - serializer_class = serializers.AssetGrantedSerializer - filterset_class = AssetFilterSet - search_fields = ['name', 'address', 'comment'] - ordering_fields = ("name", "address") - ordering = ('name',) - - -class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): - """ - 将 资产 序列化成树的结构返回 - """ - filter_queryset: callable - get_queryset: callable - - filterset_fields = ['name', 'address', 'id', 'comment'] - search_fields = ['name', 'address', 'comment'] - - def list(self, request: Request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - - if request.query_params.get('search'): - # 如果用户搜索的条件不精准,会导致返回大量的无意义数据。 - # 这里限制一下返回数据的最大条数 - queryset = queryset[:999] - queryset = sorted(queryset, key=lambda asset: asset.name) - data = self.serialize_assets(queryset, None) - return Response(data=data) diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index 6a57675f8..2fcfa5e2c 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -1,133 +1,49 @@ # -*- coding: utf-8 -*- # import abc -from rest_framework.request import Request -from rest_framework.response import Response from rest_framework.generics import ListAPIView -from common.utils import get_logger -from assets.api.mixin import SerializeToTreeNodeMixin +from assets.models import Node from perms import serializers -from perms.hands import User from perms.utils.user_permission import UserGrantedNodesQueryUtils +from common.utils import get_logger, lazyproperty -from .mixin import SelfOrPKUserMixin, RebuildTreeMixin +from .mixin import SelfOrPKUserMixin logger = get_logger(__name__) __all__ = [ - 'UserPermedNodesApi', - 'UserPermedNodesAsTreeApi', + 'UserAllPermedNodesApi', 'UserPermedNodeChildrenApi', - 'UserPermedNodeChildrenAsTreeApi', - 'BaseGrantedNodeAsTreeApi', - 'UserGrantedNodesMixin', ] -class _GrantedNodeStructApi(ListAPIView, metaclass=abc.ABCMeta): - @property - def user(self): - raise NotImplementedError - - def get_nodes(self): - # 不使用 `get_queryset` 单独定义 `get_nodes` 的原因是 - # `get_nodes` 返回的不一定是 `queryset` - raise NotImplementedError - - -class NodeChildrenMixin: - def get_children(self): - raise NotImplementedError - - def get_nodes(self): - nodes = self.get_children() - return nodes - - -class BaseGrantedNodeApi(_GrantedNodeStructApi, metaclass=abc.ABCMeta): +class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView): serializer_class = serializers.NodeGrantedSerializer - def list(self, request, *args, **kwargs): - nodes = self.get_nodes() - serializer = self.get_serializer(nodes, many=True) - return Response(serializer.data) - - -class BaseNodeChildrenApi(NodeChildrenMixin, BaseGrantedNodeApi, metaclass=abc.ABCMeta): - pass - - -class BaseGrantedNodeAsTreeApi(SerializeToTreeNodeMixin, _GrantedNodeStructApi, metaclass=abc.ABCMeta): - def list(self, request: Request, *args, **kwargs): - nodes = self.get_nodes() - nodes = self.serialize_nodes(nodes, with_asset_amount=True) - return Response(data=nodes) - - -class BaseNodeChildrenAsTreeApi(NodeChildrenMixin, BaseGrantedNodeAsTreeApi, metaclass=abc.ABCMeta): - pass - - -class UserGrantedNodeChildrenMixin: - user: User - request: Request - - def get_children(self): - user = self.user - key = self.request.query_params.get('key') - nodes = UserGrantedNodesQueryUtils(user).get_node_children(key) - return nodes - - -class UserGrantedNodesMixin: - """ - 查询用户授权的所有节点 直接授权节点 + 授权资产关联的节点 - """ - user: User + def get_queryset(self): + if getattr(self, 'swagger_fake_view', False): + return Node.objects.none() + return self.get_nodes() + @abc.abstractmethod def get_nodes(self): - utils = UserGrantedNodesQueryUtils(self.user) - nodes = utils.get_whole_tree_nodes() - return nodes + return [] + + @lazyproperty + def query_node_util(self): + return UserGrantedNodesQueryUtils(self.user) -# API - - -class UserPermedNodeChildrenApi( - SelfOrPKUserMixin, - UserGrantedNodeChildrenMixin, - BaseNodeChildrenApi -): - """ 用户授权的节点下的子节点""" - pass - - -class UserPermedNodeChildrenAsTreeApi( - SelfOrPKUserMixin, - RebuildTreeMixin, - UserGrantedNodeChildrenMixin, - BaseNodeChildrenAsTreeApi -): - """ 用户授权的节点下的子节点树""" - pass - - -class UserPermedNodesApi( - SelfOrPKUserMixin, - UserGrantedNodesMixin, - BaseGrantedNodeApi -): +class UserAllPermedNodesApi(BaseUserPermedNodesApi): """ 用户授权的节点 """ - pass + def get_nodes(self): + return self.query_node_util.get_whole_tree_nodes() -class UserPermedNodesAsTreeApi( - SelfOrPKUserMixin, - RebuildTreeMixin, - UserGrantedNodesMixin, - BaseGrantedNodeAsTreeApi -): - """ 用户授权的节点树 """ - pass +class UserPermedNodeChildrenApi(BaseUserPermedNodesApi): + """ 用户授权的节点下的子节点 """ + def get_nodes(self): + key = self.request.query_params.get('key') + nodes = self.query_node_util.get_node_children(key) + return nodes diff --git a/apps/perms/api/user_permission/nodes_with_assets.py b/apps/perms/api/user_permission/nodes_with_assets.py deleted file mode 100644 index 20cda7e00..000000000 --- a/apps/perms/api/user_permission/nodes_with_assets.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.conf import settings -from django.db.models import F, Value, CharField -from rest_framework.generics import ListAPIView -from rest_framework.request import Request -from rest_framework.response import Response - -from common.utils import get_logger, get_object_or_none -from common.utils.common import timeit -from common.permissions import IsValidUser - -from assets.models import Asset -from assets.api import SerializeToTreeNodeMixin -from perms.hands import Node -from perms.models import AssetPermission, PermNode -from perms.utils.user_permission import ( - UserGrantedTreeBuildUtils, get_user_all_asset_perm_ids, - UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, -) - -from .mixin import SelfOrPKUserMixin, RebuildTreeMixin - -logger = get_logger(__name__) - - -class MyGrantedNodesWithAssetsAsTreeApi(SerializeToTreeNodeMixin, ListAPIView): - permission_classes = (IsValidUser,) - - @timeit - def add_ungrouped_resource(self, data: list, nodes_query_utils, assets_query_utils): - if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - return - ungrouped_node = nodes_query_utils.get_ungrouped_node() - - direct_granted_assets = assets_query_utils.get_direct_granted_assets().annotate( - parent_key=Value(ungrouped_node.key, output_field=CharField()) - ).prefetch_related('platform') - - data.extend(self.serialize_nodes([ungrouped_node], with_asset_amount=True)) - data.extend(self.serialize_assets(direct_granted_assets)) - - @timeit - def add_favorite_resource(self, data: list, nodes_query_utils, assets_query_utils): - favorite_node = nodes_query_utils.get_favorite_node() - - favorite_assets = assets_query_utils.get_favorite_assets() - favorite_assets = favorite_assets.annotate( - parent_key=Value(favorite_node.key, output_field=CharField()) - ).prefetch_related('platform') - - data.extend(self.serialize_nodes([favorite_node], with_asset_amount=True)) - data.extend(self.serialize_assets(favorite_assets)) - - @timeit - def add_node_filtered_by_system_user(self, data: list, user, asset_perm_ids): - utils = UserGrantedTreeBuildUtils(user, asset_perm_ids) - nodes = utils.get_whole_tree_nodes() - data.extend(self.serialize_nodes(nodes, with_asset_amount=True)) - - def add_assets(self, data: list, assets_query_utils: UserGrantedAssetsQueryUtils): - if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - all_assets = assets_query_utils.get_direct_granted_nodes_assets() - else: - all_assets = assets_query_utils.get_all_granted_assets() - all_assets = all_assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform') - data.extend(self.serialize_assets(all_assets)) - - def list(self, request: Request, *args, **kwargs): - """ - 此算法依赖 UserGrantedMappingNode - 获取所有授权的节点和资产 - - Node = UserGrantedMappingNode + 授权节点的子节点 - Asset = 授权节点的资产 + 直接授权的资产 - """ - - user = request.user - data = [] - asset_perm_ids = get_user_all_asset_perm_ids(user) - - system_user_id = request.query_params.get('system_user') - if system_user_id: - asset_perm_ids = list(AssetPermission.objects.valid().filter( - id__in=asset_perm_ids, system_users__id=system_user_id, actions__gt=0 - ).values_list('id', flat=True).distinct()) - - nodes_query_utils = UserGrantedNodesQueryUtils(user, asset_perm_ids) - assets_query_utils = UserGrantedAssetsQueryUtils(user, asset_perm_ids) - - self.add_ungrouped_resource(data, nodes_query_utils, assets_query_utils) - self.add_favorite_resource(data, nodes_query_utils, assets_query_utils) - - if system_user_id: - # 有系统用户筛选的需要重新计算树结构 - self.add_node_filtered_by_system_user(data, user, asset_perm_ids) - else: - all_nodes = nodes_query_utils.get_whole_tree_nodes(with_special=False) - data.extend(self.serialize_nodes(all_nodes, with_asset_amount=True)) - - self.add_assets(data, assets_query_utils) - return Response(data=data) - - -class GrantedNodeChildrenWithAssetsAsTreeApiMixin(SerializeToTreeNodeMixin, - ListAPIView): - """ - 带资产的授权树 - """ - user: None - - def ensure_key(self): - key = self.request.query_params.get('key', None) - id = self.request.query_params.get('id', None) - - if key is not None: - return key - - node = get_object_or_none(Node, id=id) - if node: - return node.key - - def list(self, request: Request, *args, **kwargs): - user = self.user - key = self.ensure_key() - - nodes_query_utils = UserGrantedNodesQueryUtils(user) - assets_query_utils = UserGrantedAssetsQueryUtils(user) - - nodes = PermNode.objects.none() - assets = Asset.objects.none() - all_tree_nodes = [] - - if not key: - nodes = nodes_query_utils.get_top_level_nodes() - elif key == PermNode.UNGROUPED_NODE_KEY: - assets = assets_query_utils.get_ungroup_assets() - elif key == PermNode.FAVORITE_NODE_KEY: - assets = assets_query_utils.get_favorite_assets() - else: - nodes = nodes_query_utils.get_node_children(key) - assets = assets_query_utils.get_node_assets(key) - assets = assets.prefetch_related('platform') - - tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True) - tree_assets = self.serialize_assets(assets, key) - all_tree_nodes.extend(tree_nodes) - all_tree_nodes.extend(tree_assets) - return Response(data=all_tree_nodes) - - -class UserGrantedNodeChildrenWithAssetsAsTreeApi( - SelfOrPKUserMixin, - RebuildTreeMixin, - GrantedNodeChildrenWithAssetsAsTreeApiMixin -): - """ 用户授权的节点的子节点与资产树 """ - pass diff --git a/apps/perms/api/user_permission/tree/__init__.py b/apps/perms/api/user_permission/tree/__init__.py new file mode 100644 index 000000000..d0e88bc45 --- /dev/null +++ b/apps/perms/api/user_permission/tree/__init__.py @@ -0,0 +1,3 @@ +from .asset import * +from .node import * +from .node_with_asset import * diff --git a/apps/perms/api/user_permission/tree/asset.py b/apps/perms/api/user_permission/tree/asset.py new file mode 100644 index 000000000..081dbc756 --- /dev/null +++ b/apps/perms/api/user_permission/tree/asset.py @@ -0,0 +1,48 @@ +from django.conf import settings +from rest_framework.response import Response + +from assets.models import Asset +from assets.api import SerializeToTreeNodeMixin +from common.utils import get_logger + +from ..assets import UserDirectPermedAssetsApi +from .mixin import RebuildTreeMixin + +logger = get_logger(__name__) + + +__all__ = [ + 'UserDirectPermedAssetsAsTreeApi', + 'UserUngroupAssetsAsTreeApi', +] + + +class AssetTreeMixin(RebuildTreeMixin, SerializeToTreeNodeMixin): + """ 将资产序列化成树节点的结构返回 """ + filter_queryset: callable + get_queryset: callable + + ordering = ('name',) + filterset_fields = ('id', 'name', 'address', 'comment') + search_fields = ('name', 'address', 'comment') + + def list(self, request, *args, **kwargs): + assets = self.filter_queryset(self.get_queryset()) + if request.query_params.get('search'): + """ 限制返回数量, 搜索的条件不精准时,会返回大量的无意义数据 """ + assets = assets[:999] + data = self.serialize_assets(assets, None) + return Response(data=data) + + +class UserDirectPermedAssetsAsTreeApi(AssetTreeMixin, UserDirectPermedAssetsApi): + """ 用户 '直接授权的资产' 作为树 """ + pass + + +class UserUngroupAssetsAsTreeApi(UserDirectPermedAssetsAsTreeApi): + """ 用户 '未分组节点的资产(直接授权的资产)' 作为树 """ + def get_assets(self): + if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + return super().get_assets() + return Asset.objects.none() diff --git a/apps/perms/api/user_permission/tree/mixin.py b/apps/perms/api/user_permission/tree/mixin.py new file mode 100644 index 000000000..cbb405994 --- /dev/null +++ b/apps/perms/api/user_permission/tree/mixin.py @@ -0,0 +1,17 @@ +from rest_framework.request import Request + +from users.models import User +from perms.utils.user_permission import UserGrantedTreeRefreshController +from common.http import is_true + + +__all__ = ['RebuildTreeMixin'] + + +class RebuildTreeMixin: + user: User + + def get(self, request: Request, *args, **kwargs): + force = is_true(request.query_params.get('rebuild_tree')) + UserGrantedTreeRefreshController(self.user).refresh_if_need(force) + return super().get(request, *args, **kwargs) diff --git a/apps/perms/api/user_permission/tree/node.py b/apps/perms/api/user_permission/tree/node.py new file mode 100644 index 000000000..5872753c5 --- /dev/null +++ b/apps/perms/api/user_permission/tree/node.py @@ -0,0 +1,39 @@ +from rest_framework.response import Response + +from assets.api import SerializeToTreeNodeMixin +from common.utils import get_logger + +from .mixin import RebuildTreeMixin +from ..nodes import ( + UserAllPermedNodesApi, + UserPermedNodeChildrenApi, +) + +logger = get_logger(__name__) + +__all__ = [ + 'UserAllPermedNodesAsTreeApi', + 'UserPermedNodeChildrenAsTreeApi', +] + + +class NodeTreeMixin(RebuildTreeMixin, SerializeToTreeNodeMixin): + filter_queryset: callable + get_queryset: callable + + def list(self, request, *args, **kwargs): + nodes = self.filter_queryset(self.get_queryset()) + data = self.serialize_nodes(nodes, with_asset_amount=True) + return Response(data) + + +class UserAllPermedNodesAsTreeApi(NodeTreeMixin, UserAllPermedNodesApi): + """ 用户 '授权的节点' 作为树 """ + pass + + +class UserPermedNodeChildrenAsTreeApi(NodeTreeMixin, UserPermedNodeChildrenApi): + """ 用户授权的节点下的子节点树 """ + pass + + diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py new file mode 100644 index 000000000..d5df7ba19 --- /dev/null +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -0,0 +1,125 @@ +import abc + +from django.conf import settings +from django.db.models import F, Value, CharField +from rest_framework.generics import ListAPIView +from rest_framework.response import Response + +from assets.models import Asset +from assets.api import SerializeToTreeNodeMixin +from perms.hands import Node +from perms.models import PermNode +from perms.utils.user_permission import ( + UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, +) +from perms.utils.permission import AssetPermissionUtil +from common.utils import get_object_or_none, lazyproperty +from common.utils.common import timeit + + +from ..mixin import SelfOrPKUserMixin +from .mixin import RebuildTreeMixin + +__all__ = [ + 'UserPermedNodesWithAssetsAsTreeApi', + 'UserPermedNodeChildrenWithAssetsAsTreeApi' +] + + +class BaseUserNodeWithAssetAsTreeApi(SelfOrPKUserMixin, RebuildTreeMixin, SerializeToTreeNodeMixin, + ListAPIView): + + def list(self, request, *args, **kwargs): + nodes, assets = self.get_nodes_assets() + tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True) + tree_assets = self.serialize_assets(assets, node_key=self.node_key_for_serializer_assets) + data = list(tree_nodes) + list(tree_assets) + return Response(data=data) + + @abc.abstractmethod + def get_nodes_assets(self): + return [], [] + + @lazyproperty + def node_key_for_serializer_assets(self): + return None + + +class UserPermedNodesWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): + query_node_util: UserGrantedNodesQueryUtils + query_asset_util: UserGrantedAssetsQueryUtils + + def get_nodes_assets(self): + perm_ids = AssetPermissionUtil().get_permissions_for_user(self.request.user, flat=True) + self.query_node_util = UserGrantedNodesQueryUtils(self.request.user, perm_ids) + self.query_asset_util = UserGrantedAssetsQueryUtils(self.request.user, perm_ids) + ung_nodes, ung_assets = self._get_nodes_assets_for_ungrouped() + fav_nodes, fav_assets = self._get_nodes_assets_for_favorite() + all_nodes, all_assets = self._get_nodes_assets_for_all() + nodes = list(ung_nodes) + list(fav_nodes) + list(all_nodes) + assets = list(ung_assets) + list(fav_assets) + list(all_assets) + return nodes, assets + + @timeit + def _get_nodes_assets_for_ungrouped(self): + if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + return [], [] + node = self.query_node_util.get_ungrouped_node() + assets = self.query_asset_util.get_ungroup_assets() + assets = assets.annotate(parent_key=Value(node.key, output_field=CharField())) \ + .prefetch_related('platform') + return [node], assets + + @timeit + def _get_nodes_assets_for_favorite(self): + node = self.query_node_util.get_favorite_node() + assets = self.query_asset_util.get_favorite_assets() + assets = assets.annotate(parent_key=Value(node.key, output_field=CharField())) \ + .prefetch_related('platform') + return [node], assets + + def _get_nodes_assets_for_all(self): + nodes = self.query_node_util.get_whole_tree_nodes(with_special=False) + if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + assets = self.query_asset_util.get_direct_granted_nodes_assets() + else: + assets = self.query_asset_util.get_all_granted_assets() + assets = assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform') + return nodes, assets + + +class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): + """ 用户授权的节点的子节点与资产树 """ + + def get_nodes_assets(self): + nodes = PermNode.objects.none() + assets = Asset.objects.none() + query_node_util = UserGrantedNodesQueryUtils(self.user) + query_asset_util = UserGrantedAssetsQueryUtils(self.user) + node_key = self.query_node_key + if not node_key: + nodes = query_node_util.get_top_level_nodes() + elif node_key == PermNode.UNGROUPED_NODE_KEY: + assets = query_asset_util.get_ungroup_assets() + elif node_key == PermNode.FAVORITE_NODE_KEY: + assets = query_asset_util.get_favorite_assets() + else: + nodes = query_node_util.get_node_children(node_key) + assets = query_asset_util.get_node_assets(node_key) + assets = assets.prefetch_related('platform') + return nodes, assets + + @lazyproperty + def query_node_key(self): + node_key = self.request.query_params.get('key', None) + if node_key is not None: + return node_key + node_id = self.request.query_params.get('id', None) + node = get_object_or_none(Node, id=node_id) + node_key = getattr(node, 'key', None) + return node_key + + @lazyproperty + def node_key_for_serializer_assets(self): + return self.query_node_key + diff --git a/apps/perms/pagination.py b/apps/perms/pagination.py index 622306924..ca20b68d4 100644 --- a/apps/perms/pagination.py +++ b/apps/perms/pagination.py @@ -16,7 +16,7 @@ class GrantedAssetPaginationBase(AssetPaginationBase): self._user = view.user -class NodeGrantedAssetPagination(GrantedAssetPaginationBase): +class NodePermedAssetPagination(GrantedAssetPaginationBase): def get_count_from_nodes(self, queryset): node = getattr(self._view, 'pagination_node', None) if node: @@ -29,7 +29,7 @@ class NodeGrantedAssetPagination(GrantedAssetPaginationBase): return None -class AllGrantedAssetPagination(GrantedAssetPaginationBase): +class AllPermedAssetPagination(GrantedAssetPaginationBase): def get_count_from_nodes(self, queryset): if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: return None diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index a85770a50..ba0619edc 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -11,12 +11,12 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField from perms.serializers.permission import ActionChoicesField __all__ = [ - 'NodeGrantedSerializer', 'AssetGrantedSerializer', + 'NodeGrantedSerializer', 'AssetPermedSerializer', 'AccountsPermedSerializer' ] -class AssetGrantedSerializer(serializers.ModelSerializer): +class AssetPermedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 """ platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index 1ff44eeee..90cb3177b 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -4,39 +4,38 @@ from .. import api user_permission_urlpatterns = [ # such as: my | self | user.id - # assets path('/assets/', api.UserAllPermedAssetsApi.as_view(), - name='user-assets'), - path('/assets/tree/', api.UserDirectPermedAssetsAsTreeApi.as_view(), - name='user-assets-as-tree'), - path('/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(), - name='user-ungroup-assets-as-tree'), - - # nodes - path('/nodes/', api.UserPermedNodesApi.as_view(), - name='user-nodes'), - path('/nodes/tree/', api.UserPermedNodesAsTreeApi.as_view(), - name='user-nodes-as-tree'), - path('/nodes/children/', api.UserPermedNodeChildrenApi.as_view(), - name='user-nodes-children'), - path('/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(), - name='user-nodes-children-as-tree'), - - # node-assets + name='user-all-assets'), + path('/nodes/ungrouped/assets/', api.UserDirectPermedAssetsApi.as_view(), + name='user-direct-assets'), + path('/nodes/favorite/assets/', api.UserFavoriteAssetsApi.as_view(), + name='user-favorite-assets'), path('/nodes//assets/', api.UserPermedNodeAssetsApi.as_view(), name='user-node-assets'), - path('/nodes/ungrouped/assets/', api.UserDirectPermedAssetsApi.as_view(), - name='user-ungrouped-assets'), - path('/nodes/favorite/assets/', api.UserFavoriteAssetsApi.as_view(), - name='user-ungrouped-assets'), + # nodes + path('/nodes/', api.UserAllPermedNodesApi.as_view(), + name='user-all-nodes'), + path('/nodes/children/', api.UserPermedNodeChildrenApi.as_view(), + name='user-node-children'), + + # tree-asset + path('/assets/tree/', api.UserDirectPermedAssetsAsTreeApi.as_view(), + name='user-direct-assets-as-tree'), + path('/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(), + name='user-ungroup-assets-as-tree'), + # tree-node + path('/nodes/tree/', api.UserAllPermedNodesAsTreeApi.as_view(), + name='user-all-nodes-as-tree'), + path('/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(), + name='user-node-children-as-tree'), + # tree-node-with-asset path('/nodes/children-with-assets/tree/', - api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), - name='user-nodes-children-with-assets-as-tree'), - - path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), - name='my-nodes-with-assets-as-tree'), + api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='user-node-children-with-assets-as-tree'), + path('/nodes-with-assets/tree/', api.UserPermedNodesWithAssetsAsTreeApi.as_view(), + name='user-nodes-with-assets-as-tree'), # accounts path('/assets//accounts/', api.UserPermedAssetAccountsApi.as_view(), diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 8e4cd9199..4358e2075 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -4,6 +4,8 @@ from perms.models import AssetPermission logger = get_logger(__file__) +__all__ = ['AssetPermissionUtil'] + class AssetPermissionUtil(object): """ 资产授权相关的方法工具 """ From 0c7de507083e9fbca1d6966234baccb2c85f397f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Dec 2022 18:58:57 +0800 Subject: [PATCH 010/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20display=20?= =?UTF-8?q?field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/label.py | 5 + apps/assets/serializers/label.py | 25 ++-- apps/assets/serializers/platform.py | 43 ++----- apps/audits/serializers.py | 69 ++--------- apps/common/drf/serializers/mixin.py | 22 +++- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/rbac/serializers/role.py | 7 +- apps/terminal/backends/command/serializers.py | 6 - apps/terminal/serializers/session.py | 10 +- apps/users/serializers/user.py | 114 ++++-------------- 11 files changed, 89 insertions(+), 220 deletions(-) diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index afad1f069..2e0ff92ee 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -4,6 +4,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from orgs.mixins.models import JMSOrgBaseModel @@ -27,6 +28,10 @@ class Label(JMSOrgBaseModel): for name in names: yield name, cls.objects.filter(name=name) + @lazyproperty + def asset_count(self): + return self.assets.count() + def __str__(self): return "{}:{}".format(self.name, self.value) diff --git a/apps/assets/serializers/label.py b/apps/assets/serializers/label.py index 450b13a44..6992f4e8d 100644 --- a/apps/assets/serializers/label.py +++ b/apps/assets/serializers/label.py @@ -1,25 +1,22 @@ # -*- coding: utf-8 -*- # -from rest_framework import serializers +from django.db.models import Count from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers from orgs.mixins.serializers import BulkOrgResourceModelSerializer - from ..models import Label class LabelSerializer(BulkOrgResourceModelSerializer): - asset_count = serializers.SerializerMethodField(label=_("Assets amount")) - category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display')) + asset_count = serializers.ReadOnlyField(label=_("Assets amount")) class Meta: model = Label fields_mini = ['id', 'name'] fields_small = fields_mini + [ - 'value', 'category', 'category_display', - 'is_active', - 'date_created', - 'comment', + 'value', 'category', 'is_active', + 'date_created', 'comment', ] fields_m2m = ['asset_count', 'assets'] fields = fields_small + fields_m2m @@ -30,14 +27,10 @@ class LabelSerializer(BulkOrgResourceModelSerializer): 'assets': {'required': False, 'label': _('Asset')} } - @staticmethod - def get_asset_count(obj): - return obj.assets.count() - - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend(['get_category_display']) - return fields + @classmethod + def setup_eager_loading(cls, queryset): + queryset = queryset.annotate(asset_count=Count('assets')) + return queryset class LabelDistinctSerializer(BulkOrgResourceModelSerializer): diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 6f7458543..297896915 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -42,20 +42,13 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): model = PlatformAutomation fields = [ "id", - "ansible_enabled", - "ansible_config", - "ping_enabled", - "ping_method", - "gather_facts_enabled", - "gather_facts_method", - "push_account_enabled", - "push_account_method", - "change_secret_enabled", - "change_secret_method", - "verify_account_enabled", - "verify_account_method", - "gather_accounts_enabled", - "gather_accounts_method", + "ansible_enabled", "ansible_config", + "ping_enabled", "ping_method", + "gather_facts_enabled", "gather_facts_method", + "push_account_enabled", "push_account_method", + "change_secret_enabled", "change_secret_method", + "verify_account_enabled", "verify_account_method", + "gather_accounts_enabled", "gather_accounts_method", ] extra_kwargs = { "ping_enabled": {"label": "启用资产探测"}, @@ -80,13 +73,8 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer): class Meta: model = PlatformProtocol fields = [ - "id", - "name", - "port", - "primary", - "default", - "required", - "secret_types", + "id", "name", "port", "primary", + "default", "required", "secret_types", "setting", ] @@ -112,17 +100,12 @@ class PlatformSerializer(WritableNestedModelSerializer): model = Platform fields_mini = ["id", "name", "internal"] fields_small = fields_mini + [ - "category", - "type", - "charset", + "category", "type", "charset", ] fields = fields_small + [ - "protocols_enabled", - "protocols", - "domain_enabled", - "su_enabled", - "su_method", - "automation", + "protocols_enabled", "protocols", + "domain_enabled", "su_enabled", + "su_method", "automation", "comment", ] extra_kwargs = { diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 10e5a7f16..3b9772106 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -44,18 +44,11 @@ class UserLoginLogSerializer(serializers.ModelSerializer): model = models.UserLoginLog fields_mini = ["id"] fields_small = fields_mini + [ - "username", - "type", - "ip", - "city", - "user_agent", - "mfa", - "reason", - "reason_display", - "backend", - "backend_display", - "status", - "datetime", + "username", "type", "ip", + "city", "user_agent", "mfa", + "reason", "reason_display", + "backend", "backend_display", + "status", "datetime", ] fields = fields_small extra_kwargs = { @@ -78,14 +71,9 @@ class OperateLogSerializer(serializers.ModelSerializer): model = models.OperateLog fields_mini = ["id"] fields_small = fields_mini + [ - "user", - "action", - "resource_type", - "resource_type_display", - "resource", - "remote_addr", - "datetime", - "org_id", + "user", "action", "resource_type", + "resource_type_display", "resource", + "remote_addr", "datetime", "org_id", ] fields = fields_small extra_kwargs = {"resource_type_display": {"label": _("Resource Type")}} @@ -101,44 +89,3 @@ class SessionAuditSerializer(serializers.ModelSerializer): class Meta: model = Session fields = "__all__" - - -# -# class CommandExecutionSerializer(serializers.ModelSerializer): -# is_success = serializers.BooleanField(read_only=True, label=_('Is success')) -# hosts_display = serializers.ListSerializer( -# child=serializers.CharField(), source='hosts', read_only=True, label=_('Hosts display') -# ) -# -# class Meta: -# model = CommandExecution -# fields_mini = ['id'] -# fields_small = fields_mini + [ -# 'command', 'is_finished', 'user', -# 'date_start', 'result', 'is_success', 'org_id' -# ] -# fields = fields_small + ['hosts', 'hosts_display', 'user_display'] -# extra_kwargs = { -# 'result': {'label': _('Result')}, # model 上的方法,只能在这修改 -# 'is_success': {'label': _('Is success')}, -# 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改 -# 'user': {'label': _('User')}, -# 'user_display': {'label': _('User display')}, -# } -# -# @classmethod -# def setup_eager_loading(cls, queryset): -# """ Perform necessary eager loading of data. """ -# queryset = queryset.prefetch_related('user', 'hosts') -# return queryset -# -# -# class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer): -# asset_display = serializers.ReadOnlyField() -# commandexecution_display = serializers.ReadOnlyField() -# -# class Meta: -# model = CommandExecution.hosts.through -# fields = [ -# 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display' -# ] diff --git a/apps/common/drf/serializers/mixin.py b/apps/common/drf/serializers/mixin.py index b0cbee27e..8d59fab1d 100644 --- a/apps/common/drf/serializers/mixin.py +++ b/apps/common/drf/serializers/mixin.py @@ -1,16 +1,15 @@ from collections import Iterable -from django.db.models import NOT_PROVIDED from django.core.exceptions import ObjectDoesNotExist -from rest_framework.utils import html +from django.db.models import NOT_PROVIDED from rest_framework import serializers -from rest_framework.settings import api_settings from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty +from rest_framework.settings import api_settings +from rest_framework.utils import html from common.drf.fields import EncryptedField -from common.utils import lazyproperty - +from ..fields import LabeledChoiceField, ObjectRelatedField __all__ = [ 'BulkSerializerMixin', 'BulkListSerializerMixin', @@ -43,6 +42,7 @@ class BulkSerializerMixin(object): Become rest_framework_bulk not support uuid as a primary key so rewrite it. https://github.com/miki725/django-rest-framework-bulk/issues/66 """ + def to_internal_value(self, data): from rest_framework_bulk import BulkListSerializer ret = super(BulkSerializerMixin, self).to_internal_value(data) @@ -308,7 +308,12 @@ class DynamicFieldsMixin: self.fields.pop(field, None) -class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin): +class RelatedModelSerializerMixin: + serializer_related_field = ObjectRelatedField + serializer_choice_field = LabeledChoiceField + + +class SomeFieldsMixin: instance: None initial_data: dict common_fields = ( @@ -342,5 +347,10 @@ class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin): return primary_names + common_names +class CommonSerializerMixin(DynamicFieldsMixin, RelatedModelSerializerMixin, + SomeFieldsMixin, DefaultValueFieldsMixin): + pass + + class CommonBulkSerializerMixin(BulkSerializerMixin, CommonSerializerMixin): pass diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 8622a74d1..6cbc3b8ea 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:03b1fcb75dae7e070f662f2ad554774d51311d2561367f5d28addc3b14899195 -size 119767 +oid sha256:5c366d6b10c4ce62bd8ed7c69ecaec5533f1a178b3cc7db4e6008769a6c8bb1f +size 119897 diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 0fc454c7f..1318974aa 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:6bd3c45b4301a45fa1b110716b3ea78bcbb53a2913f707ab754882df061256cc -size 98368 +oid sha256:9f0b10566b4d35accd3a8766b14d6903243d93c5d7c55b208d930a189e590f2f +size 106125 diff --git a/apps/rbac/serializers/role.py b/apps/rbac/serializers/role.py index 6070fc8dc..140a01401 100644 --- a/apps/rbac/serializers/role.py +++ b/apps/rbac/serializers/role.py @@ -1,6 +1,7 @@ -from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from common.drf.fields import LabeledChoiceField from users.models import User from ..models import Role @@ -8,13 +9,13 @@ __all__ = ['RoleSerializer', 'RoleUserSerializer'] class RoleSerializer(serializers.ModelSerializer): - scope_display = serializers.ReadOnlyField(source='get_scope_display', label=_('Scope display')) + scope = LabeledChoiceField(choices=Role.Scope.choices, label=_("Scope")) class Meta: model = Role fields_mini = ['id', 'name', 'display_name', 'scope'] read_only_fields = [ - 'users_amount', 'builtin', 'scope_display', + 'users_amount', 'builtin', 'date_created', 'date_updated', 'created_by', 'updated_by', ] diff --git a/apps/terminal/backends/command/serializers.py b/apps/terminal/backends/command/serializers.py index ccf6984b8..e799f92f2 100644 --- a/apps/terminal/backends/command/serializers.py +++ b/apps/terminal/backends/command/serializers.py @@ -36,16 +36,10 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer): # 限制 64 字符,不能直接迁移成 128 字符,命令表数据量会比较大 account = serializers.CharField(label=_("Account ")) output = serializers.CharField(max_length=2048, allow_blank=True, label=_("Output")) - risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) timestamp = serializers.IntegerField(label=_('Timestamp')) timestamp_display = serializers.DateTimeField(read_only=True, label=_('Datetime')) remote_addr = serializers.CharField(read_only=True, label=_('Remote Address')) - @staticmethod - def get_risk_level_display(obj): - risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES) - return risk_mapper.get(obj.risk_level) - def validate_account(self, value): if len(value) > 64: value = pretty_string(value, 64) diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py index 148217306..b8ec7bd69 100644 --- a/apps/terminal/serializers/session.py +++ b/apps/terminal/serializers/session.py @@ -1,9 +1,8 @@ +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.serializers import BulkOrgResourceModelSerializer - from assets.const import Protocol +from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import Session __all__ = [ @@ -14,7 +13,6 @@ __all__ = [ class SessionSerializer(BulkOrgResourceModelSerializer): org_id = serializers.CharField(allow_blank=True) - terminal_display = serializers.CharField(read_only=True, label=_('Terminal display')) protocol = serializers.ChoiceField(choices=Protocol.choices, label=_("Protocol")) class Meta: @@ -22,11 +20,11 @@ class SessionSerializer(BulkOrgResourceModelSerializer): fields_mini = ["id"] fields_small = fields_mini + [ "user", "asset", "user_id", "asset_id", 'account', "protocol", - "login_from", "login_from_display", "remote_addr", "is_success", + "login_from", "remote_addr", "is_success", "is_finished", "has_replay", "date_start", "date_end", ] fields_fk = ["terminal", ] - fields_custom = ["can_replay", "can_join", "can_terminate", 'terminal_display'] + fields_custom = ["can_replay", "can_join", "can_terminate"] fields = fields_small + fields_fk + fields_custom extra_kwargs = { "protocol": {'label': _('Protocol')}, diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index d84a7baa0..be083a322 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- # from functools import partial + from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField, ObjectRelatedField, LabeledChoiceField from common.drf.serializers import CommonBulkSerializerMixin -from common.validators import PhoneValidator from common.utils import pretty_string, get_logger -from common.drf.fields import EncryptedField +from common.validators import PhoneValidator from rbac.builtin import BuiltinRole -from rbac.permissions import RBACPermission from rbac.models import OrgRoleBinding, SystemRoleBinding, Role -from ..models import User +from rbac.permissions import RBACPermission from ..const import PasswordStrategy +from ..models import User __all__ = [ "UserSerializer", @@ -25,19 +26,8 @@ logger = get_logger(__file__) class RolesSerializerMixin(serializers.Serializer): - system_roles = serializers.ManyRelatedField( - child_relation=serializers.PrimaryKeyRelatedField(queryset=Role.system_roles), - label=_("System roles"), - ) - org_roles = serializers.ManyRelatedField( - required=False, - child_relation=serializers.PrimaryKeyRelatedField(queryset=Role.org_roles), - label=_("Org roles"), - ) - system_roles_display = serializers.SerializerMethodField( - label=_("System roles display") - ) - org_roles_display = serializers.SerializerMethodField(label=_("Org roles display")) + system_roles = ObjectRelatedField(queryset=Role.system_roles, label=_("System roles"), many=True) + org_roles = ObjectRelatedField(queryset=Role.org_roles, label=_("Org roles"), many=True) @staticmethod def get_system_roles_display(user): @@ -60,8 +50,8 @@ class RolesSerializerMixin(serializers.Serializer): if action in ("partial_bulk_update", "bulk_update", "partial_update", "update"): action = "create" model_cls_field_mapper = { - SystemRoleBinding: ["system_roles", "system_roles_display"], - OrgRoleBinding: ["org_roles", "system_roles_display"], + SystemRoleBinding: ["system_roles"], + OrgRoleBinding: ["org_roles"], } for model_cls, fields_names in model_cls_field_mapper.items(): @@ -79,10 +69,8 @@ class RolesSerializerMixin(serializers.Serializer): return fields -class UserSerializer( - RolesSerializerMixin, CommonBulkSerializerMixin, serializers.ModelSerializer -): - password_strategy = serializers.ChoiceField( +class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializers.ModelSerializer): + password_strategy = LabeledChoiceField( choices=PasswordStrategy.choices, default=PasswordStrategy.email, required=False, @@ -93,9 +81,6 @@ class UserSerializer( mfa_force_enabled = serializers.BooleanField( read_only=True, label=_("MFA force enabled") ) - mfa_level_display = serializers.ReadOnlyField( - source="get_mfa_level_display", label=_("MFA level display") - ) login_blocked = serializers.BooleanField(read_only=True, label=_("Login blocked")) is_expired = serializers.BooleanField(read_only=True, label=_("Is expired")) can_public_key_auth = serializers.ReadOnlyField( @@ -108,9 +93,6 @@ class UserSerializer( allow_null=True, max_length=1024, ) - # Todo: 这里看看该怎么搞 - # can_update = serializers.SerializerMethodField(label=_('Can update')) - # can_delete = serializers.SerializerMethodField(label=_('Can delete')) custom_m2m_fields = { "system_roles": [BuiltinRole.system_user], "org_roles": [BuiltinRole.org_user], @@ -122,44 +104,22 @@ class UserSerializer( fields_mini = ["id", "name", "username"] # 只能写的字段, 这个虽然无法在框架上生效,但是更多对我们是提醒 fields_write_only = [ - "password", - "public_key", + "password", "public_key", ] # small 指的是 不需要计算的直接能从一张表中获取到的数据 - fields_small = ( - fields_mini - + fields_write_only - + [ - "email", - "wechat", - "phone", - "mfa_level", - "source", - "source_display", - "can_public_key_auth", - "need_update_password", - "mfa_enabled", - "is_service_account", - "is_valid", - "is_expired", - "is_active", # 布尔字段 - "date_expired", - "date_joined", - "last_login", # 日期字段 - "created_by", - "comment", # 通用字段 - "is_wecom_bound", - "is_dingtalk_bound", - "is_feishu_bound", - "is_otp_secret_key_bound", - "wecom_id", - "dingtalk_id", - "feishu_id", - ] - ) + fields_small = fields_mini + fields_write_only + [ + "email", "wechat", "phone", "mfa_level", "source", + "need_update_password", "mfa_enabled", + "is_service_account", "is_valid", + "is_expired", "is_active", # 布尔字段 + "is_otp_secret_key_bound", "can_public_key_auth", + "date_expired", "date_joined", + "last_login", # 日期字段 + "created_by", "comment", # 通用字段 + "wecom_id", "dingtalk_id", "feishu_id", + ] # 包含不太常用的字段,可以没有 fields_verbose = fields_small + [ - "mfa_level_display", "mfa_force_enabled", "is_first_login", "date_password_last_updated", @@ -168,25 +128,14 @@ class UserSerializer( # 外键的字段 fields_fk = [] # 多对多字段 - fields_m2m = [ - "groups", - "groups_display", - "system_roles", - "org_roles", - "system_roles_display", - "org_roles_display", - ] + fields_m2m = ["groups", "system_roles", "org_roles", ] # 在serializer 上定义的字段 fields_custom = ["login_blocked", "password_strategy"] fields = fields_verbose + fields_fk + fields_m2m + fields_custom read_only_fields = [ - "date_joined", - "last_login", - "created_by", - "is_first_login", - "wecom_id", - "dingtalk_id", + "date_joined", "last_login", "created_by", + "is_first_login", "wecom_id", "dingtalk_id", "feishu_id", ] disallow_self_update_fields = ["is_active"] @@ -205,18 +154,9 @@ class UserSerializer( "is_expired": {"label": _("Is expired")}, "avatar_url": {"label": _("Avatar url")}, "created_by": {"read_only": True, "allow_blank": True}, - "groups_display": {"label": _("Groups name")}, - "source_display": {"label": _("Source name")}, - "org_role_display": {"label": _("Organization role name")}, - "role_display": {"label": _("Super role name")}, - "total_role_display": {"label": _("Total role name")}, "role": {"default": "User"}, - "is_wecom_bound": {"label": _("Is wecom bound")}, - "is_dingtalk_bound": {"label": _("Is dingtalk bound")}, - "is_feishu_bound": {"label": _("Is feishu bound")}, "is_otp_secret_key_bound": {"label": _("Is OTP bound")}, "phone": {"validators": [PhoneValidator()]}, - "system_role_display": {"label": _("System role name")}, } def validate_password(self, password): @@ -326,8 +266,6 @@ class InviteSerializer(RolesSerializerMixin, serializers.Serializer): help_text=_("For security, only list several users"), ) system_roles = None - system_roles_display = None - org_roles_display = None class ServiceAccountSerializer(serializers.ModelSerializer): From 58131a2b6842336d0bae1d923c4579ddf7a79351 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 7 Dec 2022 19:26:25 +0800 Subject: [PATCH 011/132] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20su-from-acc?= =?UTF-8?q?ounts=20API;=20=E5=89=8D=E7=AB=AF=20Select2=20=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=88=9D=E5=A7=8B=E5=8C=96=E6=97=B6=20API=20=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改原因: 前端使用 Select2 组件渲染更新账号的表单页面时,会默认先创建 spm 值, 后端调用 get_object 方法时,使用的queryset,就是spm所对应的queryset, 而 detail=True, 查询的值是当前 account_id,不在 queryset 中, 所以会导致调用父类的 get_object 方法报错,对象找不到 --- apps/assets/api/account/account.py | 6 ++---- apps/assets/serializers/account/account.py | 2 +- apps/common/urls/api_urls.py | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 714d3d5a2..36781aec1 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -1,13 +1,11 @@ +from django.shortcuts import get_object_or_404 from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.generics import CreateAPIView, ListAPIView from orgs.mixins.api import OrgBulkModelViewSet -from rbac.permissions import RBACPermission from common.mixins import RecordViewLogMixin -from common.permissions import UserConfirmation -from authentication.const import ConfirmType from assets.models import Account from assets.filters import AccountFilterSet from assets.tasks import verify_accounts_connectivity @@ -32,7 +30,7 @@ class AccountViewSet(OrgBulkModelViewSet): @action(methods=['get'], detail=True, url_path='su-from-accounts') def su_from_accounts(self, request, *args, **kwargs): - account = super().get_object() + account = get_object_or_404(Account, pk=self.kwargs['pk']) accounts = account.get_su_from_accounts() serializer = serializers.AccountSerializer(accounts, many=True) return Response(data=serializer.data) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 69c4072e8..f8186d13e 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -60,7 +60,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): ) su_from = ObjectRelatedField( required=False, queryset=Account.objects, allow_null=True, allow_empty=True, - label=_('Account'), attrs=('id', 'name', 'username') + label=_('Su from'), attrs=('id', 'name', 'username') ) class Meta(BaseAccountSerializer.Meta): diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py index 01f164b00..452e47540 100644 --- a/apps/common/urls/api_urls.py +++ b/apps/common/urls/api_urls.py @@ -8,6 +8,5 @@ from .. import api app_name = 'common' urlpatterns = [ - path('resources/cache/', - api.ResourcesIDCacheApi.as_view(), name='resources-cache'), + path('resources/cache/', api.ResourcesIDCacheApi.as_view(), name='resources-cache'), ] From c14b97419d46d2b8c2813b72817f53faf70dd0a0 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Wed, 7 Dec 2022 20:13:26 +0800 Subject: [PATCH 012/132] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E7=9A=84=E4=B8=BB=E6=9C=BA=E7=BB=9F=E8=AE=A1=EF=BC=8C?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E6=AF=8F=E5=8F=B0=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=83=85=E5=86=B5api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 9 +++-- apps/ops/api/job.py | 15 +++++++-- apps/ops/models/job.py | 62 ++++++++++++++++++++++++++++++++--- apps/ops/serializers/job.py | 5 ++- apps/ops/urls/api_urls.py | 2 +- 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 1258742e0..c88c5c95f 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -10,7 +10,7 @@ __all__ = ['JMSInventory'] class JMSInventory: def __init__(self, assets, account_policy='privileged_first', - account_prefer='root,Administrator', host_callback=None): + account_prefer='root,Administrator', host_callback=None, unique_host_name=False): """ :param assets: :param account_prefer: account username name if not set use account_policy @@ -20,6 +20,8 @@ class JMSInventory: self.account_prefer = account_prefer self.account_policy = account_policy self.host_callback = host_callback + self.exclude_hosts = {} + self.unique_host_name = unique_host_name @staticmethod def clean_assets(assets): @@ -112,6 +114,9 @@ class JMSInventory: 'secret': account.secret, 'secret_type': account.secret_type } if account else None } + if self.unique_host_name: + host['name'] += '({})'.format(asset.id) + if host['jms_account'] and asset.platform.type == 'oracle': host['jms_account']['mode'] = 'sysdba' if account.privileged else None @@ -194,7 +199,7 @@ class JMSInventory: print(_("Skip hosts below:")) for i, host in enumerate(exclude_hosts, start=1): print("{}: [{}] \t{}".format(i, host['name'], host['error'])) - + self.exclude_hosts[host['name']] = host['error'] hosts = list(filter(lambda x: not x.get('error'), hosts)) data = {'all': {'hosts': {}}} for host in hosts: diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index ee518c008..bfaccbe39 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -1,12 +1,12 @@ from rest_framework.views import APIView - +from django.shortcuts import get_object_or_404 from rest_framework.response import Response from ops.api.base import SelfBulkModelViewSet from ops.models import Job, JobExecution from ops.serializers.job import JobSerializer, JobExecutionSerializer -__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView'] +__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobAssetDetail'] from ops.tasks import run_ops_job_execution from ops.variables import JMS_JOB_VARIABLE_HELP @@ -73,3 +73,14 @@ class JobRunVariableHelpAPIView(APIView): def get(self, request, **kwargs): return Response(data=JMS_JOB_VARIABLE_HELP) + + +class JobAssetDetail(APIView): + rbac_perms = () + permission_classes = () + + def get(self, request, **kwargs): + execution_id = request.query_params.get('execution_id') + if execution_id: + execution = get_object_or_404(JobExecution, id=execution_id) + return Response(data=execution.assent_result_detail) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index e96801535..75592d66f 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -88,7 +88,7 @@ class Job(JMSBaseModel, PeriodTaskModelMixin): @property def inventory(self): - return JMSInventory(self.assets.all(), self.runas_policy, self.runas) + return JMSInventory(self.assets.all(), self.runas_policy, self.runas, unique_host_name=True) def create_execution(self): return self.executions.create() @@ -110,6 +110,55 @@ class JobExecution(JMSBaseModel): date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) + @property + def count(self): + if self.is_finished and not self.summary.get('error', None): + return { + "ok": len(self.summary['ok']), + "failed": len(self.summary['failures']) + len(self.summary['dark']), + "excludes": len(self.summary['excludes']), + "total": self.job.assets.count() + } + + @property + def assent_result_detail(self): + if self.is_finished and not self.summary.get('error', None): + result = { + "summary": self.count, + "detail": [], + } + for asset in self.job.assets.all(): + asset_detail = { + "name": asset.name, + "status": "ok", + "tasks": [], + } + host_name = "{}({})".format(asset.name, asset.id) + if self.summary["excludes"].get(host_name, None): + asset_detail.update({"status": "excludes"}) + result["detail"].append(asset_detail) + break + if self.result["dark"].get(host_name, None): + asset_detail.update({"status": "failed"}) + for key, task in self.result["dark"][host_name].items(): + task_detail = {"name": key, + "output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))} + asset_detail["tasks"].append(task_detail) + if self.result["failures"].get(host_name, None): + asset_detail.update({"status": "failed"}) + for key, task in self.result["failures"][host_name].items(): + task_detail = {"name": key, + "output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))} + asset_detail["tasks"].append(task_detail) + + if self.result["ok"].get(host_name, None): + for key, task in self.result["ok"][host_name].items(): + task_detail = {"name": key, + "output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))} + asset_detail["tasks"].append(task_detail) + result["detail"].append(asset_detail) + return result + @property def job_type(self): return self.job.type @@ -124,6 +173,11 @@ class JobExecution(JMSBaseModel): def get_runner(self): inv = self.job.inventory inv.write_to_file(self.inventory_path) + if len(inv.exclude_hosts) > 0: + self.summary['excludes'] = inv.exclude_hosts + self.result['excludes'] = inv.exclude_hosts + self.save() + if isinstance(self.parameters, str): extra_vars = json.loads(self.parameters) else: @@ -191,7 +245,7 @@ class JobExecution(JMSBaseModel): def set_error(self, error): this = self.__class__.objects.get(id=self.id) # 重新获取一次,避免数据库超时连接超时 this.status = 'failed' - this.summary['error'] = str(error) + this.summary.update({'error': str(error)}) this.finish_task() def set_result(self, cb): @@ -200,8 +254,8 @@ class JobExecution(JMSBaseModel): } this = self.__class__.objects.get(id=self.id) this.status = status_mapper.get(cb.status, cb.status) - this.summary = cb.summary - this.result = cb.result + this.summary.update(cb.summary) + this.result.update(cb.result) this.finish_task() def finish_task(self): diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 1ac944d42..d5999a8df 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -28,10 +28,13 @@ class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin): class JobExecutionSerializer(serializers.ModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) job_type = serializers.ReadOnlyField(label=_("Job type")) + count = serializers.ReadOnlyField(label=_("Count")) class Meta: model = JobExecution - read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created', + read_only_fields = ["id", "task_id", "timedelta", "count", "time_cost", 'is_finished', 'date_start', + 'date_finished', + 'date_created', 'is_success', 'task_id', 'short_id', 'job_type', 'creator'] fields = read_only_fields + [ "job", "parameters" diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index a0c2754cf..6fad435bf 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -24,7 +24,7 @@ router.register(r'task-executions', api.CeleryTaskExecutionViewSet, 'task-execut urlpatterns = [ path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'), - + path('job-execution/asset-detail/', api.JobAssetDetail.as_view(), name='asset-detail'), path('ansible/job-execution//log/', api.AnsibleTaskLogApi.as_view(), name='job-execution-log'), path('celery/task//task-execution//log/', api.CeleryTaskExecutionLogApi.as_view(), From e5afbd41183f3be477e4222b5f791add6d3c4e3a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 7 Dec 2022 23:55:56 +0800 Subject: [PATCH 013/132] perf: k8s tree api (#9169) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/mixin.py | 1 + apps/assets/utils/__init__.py | 2 + apps/assets/utils/k8s.py | 192 ++++++++++++++++++ apps/assets/{utils.py => utils/node.py} | 4 +- .../user_permission/tree/node_with_asset.py | 45 +++- apps/perms/urls/user_permission.py | 6 +- 6 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 apps/assets/utils/__init__.py create mode 100644 apps/assets/utils/k8s.py rename apps/assets/{utils.py => utils/node.py} (98%) diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 2abe967b0..6cc198169 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -61,6 +61,7 @@ class SerializeToTreeNodeMixin: 'meta': { 'type': 'asset', 'data': { + 'platform_type': asset.platform.type, 'org_name': asset.org_name, 'sftp': asset.platform_id in sftp_enabled_platform, }, diff --git a/apps/assets/utils/__init__.py b/apps/assets/utils/__init__.py new file mode 100644 index 000000000..9f588b6d2 --- /dev/null +++ b/apps/assets/utils/__init__.py @@ -0,0 +1,2 @@ +from .k8s import * +from .node import * diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py new file mode 100644 index 000000000..19134440f --- /dev/null +++ b/apps/assets/utils/k8s.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +from urllib3.exceptions import MaxRetryError +from urllib.parse import urlencode, parse_qsl + +from kubernetes import client +from kubernetes.client import api_client +from kubernetes.client.api import core_v1_api +from kubernetes.client.exceptions import ApiException + +from rest_framework.generics import get_object_or_404 + +from common.utils import get_logger +from common.tree import TreeNode +from assets.models import Account, Asset + +from ..const import CloudTypes, Category + +logger = get_logger(__file__) + + +class KubernetesClient: + def __init__(self, url, token): + self.url = url + self.token = token + + def get_api(self): + configuration = client.Configuration() + configuration.host = self.url + configuration.verify_ssl = False + configuration.api_key = {"authorization": "Bearer " + self.token} + c = api_client.ApiClient(configuration=configuration) + api = core_v1_api.CoreV1Api(c) + return api + + def get_namespace_list(self): + api = self.get_api() + namespace_list = [] + for ns in api.list_namespace().items: + namespace_list.append(ns.metadata.name) + return namespace_list + + def get_services(self): + api = self.get_api() + ret = api.list_service_for_all_namespaces(watch=False) + for i in ret.items: + print("%s \t%s \t%s \t%s \t%s \n" % ( + i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports)) + + def get_pod_info(self, namespace, pod): + api = self.get_api() + resp = api.read_namespaced_pod(namespace=namespace, name=pod) + return resp + + def get_pod_logs(self, namespace, pod): + api = self.get_api() + log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200) + return log_content + + def get_pods(self): + api = self.get_api() + try: + ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3)) + except MaxRetryError: + logger.warning('Kubernetes connection timed out') + return + except ApiException as e: + if e.status == 401: + logger.warning('Kubernetes User not authenticated') + else: + logger.warning(e) + return + data = {} + for i in ret.items: + namespace = i.metadata.namespace + pod_info = { + 'pod_name': i.metadata.name, + 'containers': [j.name for j in i.spec.containers] + } + if namespace in data: + data[namespace].append(pod_info) + else: + data[namespace] = [pod_info, ] + return data + + @staticmethod + def get_kubernetes_data(app_id, username): + asset = get_object_or_404(Asset, id=app_id) + account = get_object_or_404(Account, asset=asset, username=username) + k8s = KubernetesClient(asset.address, account.secret) + return k8s.get_pods() + + +class KubernetesTree: + def __init__(self, tree_id): + self.tree_id = str(tree_id) + + @staticmethod + def create_tree_id(pid, tp, v): + i = dict(parse_qsl(pid)) + i[tp] = v + tree_id = urlencode(i) + return tree_id + + def as_tree_node(self, app): + pid = app.create_app_tree_pid(self.tree_id) + app_id = str(app.id) + node = self.create_tree_node( + app_id, pid, app.name, 'k8s' + ) + return node + + def as_asset_tree_node(self, asset): + i = urlencode({'asset_id': self.tree_id}) + node = self.create_tree_node( + i, str(asset.id), str(asset), 'asset', + ) + return node + + def as_account_tree_node(self, account, parent_info): + username = account.username + name = f'{account.name}({account.username})' + pid = urlencode({'asset_id': self.tree_id}) + i = self.create_tree_id(pid, 'account', username) + parent_info.update({'account': username}) + node = self.create_tree_node( + i, pid, name, 'account', icon='user-tie' + ) + return node + + def as_namespace_pod_tree_node(self, name, tp, counts=0, is_container=False): + i = self.create_tree_id(self.tree_id, tp, name) + name = name if is_container else f'{name}({counts})' + node = self.create_tree_node( + i, self.tree_id, name, tp, icon='cloud', is_container=is_container + ) + return node + + @staticmethod + def create_tree_node(id_, pid, name, identity, icon='', is_container=False): + node = { + 'id': id_, + 'name': name, + 'title': name, + 'pId': pid, + 'isParent': not is_container, + 'open': False, + 'iconSkin': icon, + 'meta': { + 'type': 'k8s', + 'data': { + 'category': Category.CLOUD, + 'type': CloudTypes.K8S, + 'identity': identity + } + } + } + return node + + def async_tree_node(self, parent_info): + pod_name = parent_info.get('pod') + asset_id = parent_info.get('asset_id') + namespace = parent_info.get('namespace') + account_username = parent_info.get('account') + + tree = [] + data = KubernetesClient.get_kubernetes_data(asset_id, account_username) + if not data: + return tree + + if pod_name: + for container in next( + filter( + lambda x: x['pod_name'] == pod_name, data[namespace] + ) + )['containers']: + container_node = self.as_namespace_pod_tree_node( + container, 'container', is_container=True + ) + tree.append(container_node) + elif namespace: + for pod in data[namespace]: + pod_nodes = self.as_namespace_pod_tree_node( + pod['pod_name'], 'pod', len(pod['containers']) + ) + tree.append(pod_nodes) + elif account_username: + for namespace, pods in data.items(): + namespace_node = self.as_namespace_pod_tree_node( + namespace, 'namespace', len(pods) + ) + tree.append(namespace_node) + return tree diff --git a/apps/assets/utils.py b/apps/assets/utils/node.py similarity index 98% rename from apps/assets/utils.py rename to apps/assets/utils/node.py index 6c39fffa5..5d22421c5 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils/node.py @@ -7,8 +7,8 @@ from common.struct import Stack from common.db.models import output_as_string from orgs.utils import ensure_in_real_or_default_org, current_org -from .locks import NodeTreeUpdateLock -from .models import Node, Asset +from ..locks import NodeTreeUpdateLock +from ..models import Node, Asset logger = get_logger(__file__) diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index d5df7ba19..fd07972ff 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -1,26 +1,31 @@ import abc +from urllib.parse import parse_qsl from django.conf import settings from django.db.models import F, Value, CharField from rest_framework.generics import ListAPIView +from rest_framework.request import Request from rest_framework.response import Response +from rest_framework.generics import get_object_or_404 from assets.models import Asset +from assets.utils import KubernetesTree from assets.api import SerializeToTreeNodeMixin from perms.hands import Node from perms.models import PermNode from perms.utils.user_permission import ( UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, ) +from perms.utils import PermAccountUtil from perms.utils.permission import AssetPermissionUtil from common.utils import get_object_or_none, lazyproperty from common.utils.common import timeit - from ..mixin import SelfOrPKUserMixin from .mixin import RebuildTreeMixin __all__ = [ + 'UserGrantedK8sAsTreeApi', 'UserPermedNodesWithAssetsAsTreeApi', 'UserPermedNodeChildrenWithAssetsAsTreeApi' ] @@ -123,3 +128,41 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): def node_key_for_serializer_assets(self): return self.query_node_key + +class UserGrantedK8sAsTreeApi( + SelfOrPKUserMixin, + ListAPIView +): + """ 用户授权的K8s树 """ + + @staticmethod + def asset(asset_id): + kwargs = {'id': asset_id, 'is_active': True} + asset = get_object_or_404(Asset, **kwargs) + return asset + + def list(self, request: Request, *args, **kwargs): + tree_id = request.query_params.get('tree_id') + key = request.query_params.get('key', {}) + + tree = [] + util = PermAccountUtil() + parent_info = dict(parse_qsl(key)) + account_username = parent_info.get('account') + + asset_id = parent_info.get('asset_id') + asset_id = tree_id if not asset_id else asset_id + + if tree_id and not account_username: + asset = self.asset(asset_id) + accounts = util.get_permed_accounts_for_user(self.user, asset) + asset_node = KubernetesTree(tree_id).as_asset_tree_node(asset) + tree.append(asset_node) + for account in accounts: + account_node = KubernetesTree(tree_id).as_account_tree_node( + account, parent_info, + ) + tree.append(account_node) + else: + tree = KubernetesTree(key).async_tree_node(parent_info) + return Response(data=tree) diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index 90cb3177b..e76f29ed1 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -34,9 +34,13 @@ user_permission_urlpatterns = [ path('/nodes/children-with-assets/tree/', api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-node-children-with-assets-as-tree'), + + path('/nodes/children-with-k8s/tree/', + api.UserGrantedK8sAsTreeApi.as_view(), + name='user-nodes-children-with-k8s-as-tree'), + path('/nodes-with-assets/tree/', api.UserPermedNodesWithAssetsAsTreeApi.as_view(), name='user-nodes-with-assets-as-tree'), - # accounts path('/assets//accounts/', api.UserPermedAssetAccountsApi.as_view(), name='user-permed-asset-accounts'), From beac2a15145e72cea75e134f04e0d1dafca6eba0 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 8 Dec 2022 13:37:35 +0800 Subject: [PATCH 014/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=B4=A6=E5=8F=B7=20su-from-accounts=20=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=8C=85=E5=90=AB=E8=87=AA=E5=B7=B1=E5=92=8C?= =?UTF-8?q?=E4=BB=A5=E8=87=AA=E5=B7=B1=E4=B8=BA=20su-from=20=E7=9A=84?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 7703ac1e6..dbdcd21fd 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -88,7 +88,8 @@ class Account(AbsConnectivity, BaseAccount): return cls(name=cls.AliasAccount.USER.label, username=cls.AliasAccount.USER.value) def get_su_from_accounts(self): - return self.asset.accounts.exclude(id=self.id) + """ 排除自己和以自己为 su-from 的账号 """ + return self.asset.accounts.exclude(id=self.id).exclude(su_from=self) class AccountTemplate(BaseAccount): From 0ae9b76f04001a2af990dda804f28fc2cffba894 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 9 Dec 2022 10:21:36 +0800 Subject: [PATCH 015/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=20(#9179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化构建 * fix: 修正构建错误 * perf: 优化构建依赖包 * fix: 修正构建判断 * perf: 现阶段还需要 debug 工具 Co-authored-by: 吴小白 <296015668@qq.com> --- Dockerfile | 16 ++++++---------- Dockerfile-ee | 10 ++++++++++ Dockerfile.loong64 | 18 +++++++----------- requirements/requirements.txt | 24 +----------------------- requirements/requirements_xpack.txt | 24 ++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 44 deletions(-) create mode 100644 Dockerfile-ee create mode 100644 requirements/requirements_xpack.txt diff --git a/Dockerfile b/Dockerfile index f236c86eb..9bb5f2f8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,6 @@ ARG BUILD_DEPENDENCIES=" \ pkg-config" ARG DEPENDENCIES=" \ - default-libmysqlclient-dev \ freetds-dev \ libpq-dev \ libffi-dev \ @@ -28,20 +27,15 @@ ARG DEPENDENCIES=" \ libxml2-dev \ libxmlsec1-dev \ libxmlsec1-openssl \ - libaio-dev \ - openssh-client \ - sshpass" + libaio-dev" ARG TOOLS=" \ ca-certificates \ - curl \ - default-mysql-client \ - iputils-ping \ + default-libmysqlclient-dev \ locales \ - procps \ - redis-tools \ + openssh-client \ + sshpass \ telnet \ - vim \ unzip \ wget" @@ -82,6 +76,8 @@ ENV PIP_MIRROR=$PIP_MIRROR ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR +ARG DEBUG + RUN --mount=type=cache,target=/root/.cache/pip \ set -ex \ && pip config set global.index-url ${PIP_MIRROR} \ diff --git a/Dockerfile-ee b/Dockerfile-ee new file mode 100644 index 000000000..63c65d21c --- /dev/null +++ b/Dockerfile-ee @@ -0,0 +1,10 @@ +ARG VERSION +FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack +FROM jumpserver/core:${VERSION} +COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack + +WORKDIR /opt/jumpserver + +RUN --mount=type=cache,target=/root/.cache/pip \ + set -ex \ + && pip install -r requirements/requirements_xpack.txt diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index c2fa521b6..6a1d9a2f1 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -18,7 +18,6 @@ ARG BUILD_DEPENDENCIES=" \ pkg-config" ARG DEPENDENCIES=" \ - default-libmysqlclient-dev \ freetds-dev \ libpq-dev \ libffi-dev \ @@ -28,20 +27,15 @@ ARG DEPENDENCIES=" \ libxml2-dev \ libxmlsec1-dev \ libxmlsec1-openssl \ - libaio-dev \ - openssh-client \ - sshpass" + libaio-dev" ARG TOOLS=" \ ca-certificates \ - curl \ - default-mysql-client \ - iputils-ping \ + default-libmysqlclient-dev \ locales \ - netcat \ - redis-server \ + openssh-client \ + sshpass \ telnet \ - vim \ unzip \ wget" @@ -69,12 +63,14 @@ ENV PIP_MIRROR=$PIP_MIRROR ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR +ARG DEBUG + RUN --mount=type=cache,target=/root/.cache/pip \ set -ex \ && pip config set global.index-url ${PIP_MIRROR} \ && pip install --upgrade pip \ && pip install --upgrade setuptools wheel \ - && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.1-cp38-cp38-linux_loongarch64.whl \ + && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.2-cp38-cp38-linux_loongarch64.whl \ && pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \ && pip install $(grep 'PyNaCl' requirements/requirements.txt) \ && GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \ diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 5baa5b953..33494d442 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -25,7 +25,7 @@ paramiko==2.11.0 passlib==1.7.4 pyasn1==0.4.8 pycparser==2.21 -cryptography==37.0.4 +cryptography==36.0.2 pycryptodome==3.15.0 pycryptodomex==3.15.0 gmssl==3.2.1 @@ -87,7 +87,6 @@ pytz==2022.1 # Runtime django-proxy==1.2.1 channels-redis==3.4.0 -channels==3.0.4 python-daemon==2.3.0 eventlet==0.33.1 greenlet==1.1.2 @@ -107,23 +106,6 @@ django-cas-ng==4.0.1 python-cas==1.5.0 django-auth-ldap==2.2.0 # Cloud req -qingcloud-sdk==1.2.12 -azure-mgmt-subscription==1.0.0 -azure-identity==1.5.0 -azure-mgmt-compute==4.6.2 -azure-mgmt-network==2.7.0 -google-cloud-compute==0.5.0 -alibabacloud_dysmsapi20170525==2.0.2 -python-novaclient==11.0.1 -python-keystoneclient==4.3.0 -bce-python-sdk==0.8.64 -tencentcloud-sdk-python==3.0.662 -aliyun-python-sdk-core-v3==2.9.1 -aliyun-python-sdk-ecs==4.10.1 -huaweicloud-sdk-python==1.0.21 -# python-keystoneclient need keystoneauth1>=3.4.0 -# huaweicloud-sdk-python need keystoneauth1<=3.4.0 -keystoneauth1==3.4.0 boto3==1.24.12 botocore==1.27.12 s3transfer==0.6.0 @@ -131,8 +113,6 @@ kubernetes==21.7.0 # DB requirements mysqlclient==2.1.0 PyMySQL==1.0.2 -oracledb==1.0.1 -psycopg2-binary==2.9.1 pymssql==2.2.5 django-mysql==3.9.0 django-redis==5.2.0 @@ -146,5 +126,3 @@ ipython==8.4.0 ForgeryPy3==0.3.1 django-debug-toolbar==3.5 Pympler==1.0.1 -IPy==1.1 -psycopg2==2.9.4 diff --git a/requirements/requirements_xpack.txt b/requirements/requirements_xpack.txt new file mode 100644 index 000000000..4f0f0ea7d --- /dev/null +++ b/requirements/requirements_xpack.txt @@ -0,0 +1,24 @@ +# Cloud req +qingcloud-sdk==1.2.12 +azure-mgmt-subscription==1.0.0 +azure-identity==1.5.0 +azure-mgmt-compute==4.6.2 +azure-mgmt-network==2.7.0 +google-cloud-compute==0.5.0 +alibabacloud_dysmsapi20170525==2.0.2 +python-novaclient==11.0.1 +python-keystoneclient==4.3.0 +bce-python-sdk==0.8.64 +tencentcloud-sdk-python==3.0.662 +aliyun-python-sdk-core-v3==2.9.1 +aliyun-python-sdk-ecs==4.10.1 +huaweicloud-sdk-python==1.0.21 +# python-keystoneclient need keystoneauth1>=3.4.0 +# huaweicloud-sdk-python need keystoneauth1<=3.4.0 +keystoneauth1==3.4.0 +# DB requirements +oracledb==1.0.1 +psycopg2-binary==2.9.1 +pymssql==2.2.5 +IPy==1.1 +psycopg2==2.9.4 From ddb731c5cd3f6e6779cec77e528e285599a7467b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Fri, 9 Dec 2022 10:27:50 +0800 Subject: [PATCH 016/132] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20python3.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- Dockerfile.loong64 | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9bb5f2f8c..75e5e5c78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-slim as stage-build +FROM python:3.9-slim as stage-build ARG TARGETARCH ARG VERSION @@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver ADD . . RUN cd utils && bash -ixeu build.sh -FROM python:3.8-slim +FROM python:3.9-slim ARG TARGETARCH MAINTAINER JumpServer Team diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 6a1d9a2f1..86e9cd7f7 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -1,4 +1,4 @@ -FROM python:3.8-slim as stage-build +FROM python:3.9-slim as stage-build ARG TARGETARCH ARG VERSION @@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver ADD . . RUN cd utils && bash -ixeu build.sh -FROM python:3.8-slim +FROM python:3.9-slim ARG TARGETARCH MAINTAINER JumpServer Team @@ -70,8 +70,8 @@ RUN --mount=type=cache,target=/root/.cache/pip \ && pip config set global.index-url ${PIP_MIRROR} \ && pip install --upgrade pip \ && pip install --upgrade setuptools wheel \ - && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.2-cp38-cp38-linux_loongarch64.whl \ - && pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \ + && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.2-cp39-cp39-linux_loongarch64.whl \ + && pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl \ && pip install $(grep 'PyNaCl' requirements/requirements.txt) \ && GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \ && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ From a7354d949de458309bbc233d3c3f92d73c84be0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Fri, 9 Dec 2022 11:06:58 +0800 Subject: [PATCH 017/132] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E6=A3=80=E6=9F=A5=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 + Dockerfile.loong64 | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 75e5e5c78..6898a63c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ ARG DEPENDENCIES=" \ ARG TOOLS=" \ ca-certificates \ + curl \ default-libmysqlclient-dev \ locales \ openssh-client \ diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 86e9cd7f7..27002794f 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -31,6 +31,7 @@ ARG DEPENDENCIES=" \ ARG TOOLS=" \ ca-certificates \ + curl \ default-libmysqlclient-dev \ locales \ openssh-client \ From 8beb1b81cf39e2122e6851ea438aceea45dbd4fa Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 9 Dec 2022 11:12:56 +0800 Subject: [PATCH 018/132] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20AssetSerial?= =?UTF-8?q?izer=20=E7=BB=A7=E6=89=BF=20BulkOrgResourceSerializerMixin=20?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 188539981..e34af1f5f 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -8,7 +8,7 @@ from django.db.models import F from common.drf.serializers import WritableNestedModelSerializer from common.drf.fields import LabeledChoiceField, ObjectRelatedField -from orgs.mixins.serializers import OrgResourceSerializerMixin +from orgs.mixins.serializers import BulkOrgResourceSerializerMixin from ..account import AccountSerializer from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol from ...const import Category, AllTypes @@ -58,7 +58,7 @@ class AssetAccountSerializer(AccountSerializer): fields = fields_mini + fields_write_only -class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer): +class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSerializer): 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=Domain.objects, label=_('Domain'), allow_null=True) From 4f5cc56b00b016fea787e06af6e3fbfcbdc07382 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 8 Dec 2022 19:30:16 +0800 Subject: [PATCH 019/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20UserPermTr?= =?UTF-8?q?eeUtil=20=E9=80=BB=E8=BE=91(=E8=BF=9B=E8=A1=8C=E4=B8=AD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset/common.py | 21 +- apps/perms/api/user_permission/tree/mixin.py | 4 +- apps/perms/locks.py | 2 +- apps/perms/signal_handlers/refresh_perms.py | 22 +- apps/perms/tasks.py | 4 +- apps/perms/utils/permission.py | 45 ++-- apps/perms/utils/user_permission.py | 235 ++++++++++++------- apps/settings/signal_handlers.py | 4 +- 8 files changed, 210 insertions(+), 127 deletions(-) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 81693252d..c191dafe4 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -63,18 +63,29 @@ class NodesRelationMixin: nodes = Node.objects.filter(id=Node.org_root().id) return nodes - def get_all_nodes(self, flat=False): + def get_all_nodes(self, flat=False, only_keys=False): from ..node import Node node_keys = set() for node in self.get_nodes(): ancestor_keys = node.get_ancestor_keys(with_self=True) node_keys.update(ancestor_keys) + if only_keys: + return node_keys nodes = Node.objects.filter(key__in=node_keys).distinct() - if flat: - node_ids = set(nodes.values_list('id', flat=True)) - return node_ids - else: + if not flat: return nodes + node_ids = set(nodes.values_list('id', flat=True)) + return node_ids + + @classmethod + def get_all_nodes_for_assets(cls, assets): + from ..node import Node + node_keys = set() + for asset in assets: + asset_node_keys = asset.get_all_nodes(only_keys=True) + node_keys.update(asset_node_keys) + nodes = Node.objects.filter(key__in=node_keys) + return nodes class Protocol(models.Model): diff --git a/apps/perms/api/user_permission/tree/mixin.py b/apps/perms/api/user_permission/tree/mixin.py index cbb405994..87c4eba3c 100644 --- a/apps/perms/api/user_permission/tree/mixin.py +++ b/apps/perms/api/user_permission/tree/mixin.py @@ -1,7 +1,7 @@ from rest_framework.request import Request from users.models import User -from perms.utils.user_permission import UserGrantedTreeRefreshController +from perms.utils.user_permission import UserPermTreeUtil from common.http import is_true @@ -13,5 +13,5 @@ class RebuildTreeMixin: def get(self, request: Request, *args, **kwargs): force = is_true(request.query_params.get('rebuild_tree')) - UserGrantedTreeRefreshController(self.user).refresh_if_need(force) + UserPermTreeUtil(self.user).refresh_if_need(force) return super().get(request, *args, **kwargs) diff --git a/apps/perms/locks.py b/apps/perms/locks.py index 96c766fb8..77babf7f8 100644 --- a/apps/perms/locks.py +++ b/apps/perms/locks.py @@ -2,7 +2,7 @@ from common.utils.lock import DistributedLock class UserGrantedTreeRebuildLock(DistributedLock): - name_template = 'perms.user.asset.node.tree.rebuid.' + name_template = 'perms.user.asset.node.tree.rebuild.' def __init__(self, user_id): name = self.name_template.format(user_id=user_id) diff --git a/apps/perms/signal_handlers/refresh_perms.py b/apps/perms/signal_handlers/refresh_perms.py index 2e66c4475..2d5617b34 100644 --- a/apps/perms/signal_handlers/refresh_perms.py +++ b/apps/perms/signal_handlers/refresh_perms.py @@ -10,7 +10,7 @@ from common.utils import get_logger from common.exceptions import M2MReverseNotAllowed from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR from perms.models import AssetPermission -from perms.utils.user_permission import UserGrantedTreeRefreshController +from perms.utils.user_permission import UserPermTreeUtil logger = get_logger(__file__) @@ -24,7 +24,7 @@ def on_user_group_delete(sender, instance: UserGroup, using, **kwargs): org_id = instance.org_id user_ids = UserGroup.users.through.objects.filter(usergroup_id=instance.id).values_list('user_id', flat=True) - UserGrantedTreeRefreshController.add_need_refresh_orgs_for_users([org_id], list(user_ids)) + UserPermTreeUtil.add_need_refresh_orgs_for_users([org_id], list(user_ids)) @receiver(m2m_changed, sender=User.groups.through) @@ -46,14 +46,14 @@ def on_user_groups_change(sender, instance, action, reverse, pk_set, **kwargs): return org_ids = [org_id] - UserGrantedTreeRefreshController.add_need_refresh_orgs_for_users(org_ids, user_ids) + UserPermTreeUtil.add_need_refresh_orgs_for_users(org_ids, user_ids) @receiver([pre_delete], sender=AssetPermission) def on_asset_perm_pre_delete(sender, instance, **kwargs): # 授权删除之前,查出所有相关用户 with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_by_asset_perm_ids([instance.id]) + UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) @receiver([pre_save], sender=AssetPermission) @@ -63,7 +63,7 @@ def on_asset_perm_pre_save(sender, instance, **kwargs): if old.is_valid != instance.is_valid: with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_by_asset_perm_ids([instance.id]) + UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) except AssetPermission.DoesNotExist: pass @@ -73,7 +73,7 @@ def on_asset_perm_post_save(sender, instance, created, **kwargs): if not created: return with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_by_asset_perm_ids([instance.id]) + UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) def need_rebuild_mapping_node(action): @@ -89,7 +89,7 @@ def on_permission_nodes_changed(sender, instance, action, reverse, **kwargs): return with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_by_asset_perm_ids([instance.id]) + UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) @receiver(m2m_changed, sender=AssetPermission.assets.through) @@ -100,7 +100,7 @@ def on_permission_assets_changed(sender, instance, action, reverse, pk_set, mode if not need_rebuild_mapping_node(action): return with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_by_asset_perm_ids([instance.id]) + UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) @receiver(m2m_changed, sender=AssetPermission.users.through) @@ -112,7 +112,7 @@ def on_asset_permission_users_changed(sender, action, reverse, instance, pk_set, return with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_orgs_for_users( + UserPermTreeUtil.add_need_refresh_orgs_for_users( [current_org.id], pk_set ) @@ -129,7 +129,7 @@ def on_asset_permission_user_groups_changed(sender, instance, action, pk_set, re .values_list('user_id', flat=True) \ .distinct() with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_orgs_for_users( + UserPermTreeUtil.add_need_refresh_orgs_for_users( [current_org.id], user_ids ) @@ -147,4 +147,4 @@ def on_node_asset_change(action, instance, reverse, pk_set, **kwargs): node_pk_set = pk_set with tmp_to_org(instance.org): - UserGrantedTreeRefreshController.add_need_refresh_on_nodes_assets_relate_change(node_pk_set, asset_pk_set) + UserPermTreeUtil.add_need_refresh_on_nodes_assets_relate_change(node_pk_set, asset_pk_set) diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 564e5657e..e739f4bcd 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -16,7 +16,7 @@ from perms.notifications import ( PermedAssetsWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg, ) from perms.models import AssetPermission -from perms.utils.user_permission import UserGrantedTreeRefreshController +from perms.utils.user_permission import UserPermTreeUtil logger = get_logger(__file__) @@ -52,7 +52,7 @@ def check_asset_permission_expired(): ).distinct().values_list('id', flat=True) asset_perm_ids = list(asset_perm_ids) logger.info(f'>>> checking {start} to {end} have {asset_perm_ids} expired') - UserGrantedTreeRefreshController.add_need_refresh_by_asset_perm_ids_cross_orgs(asset_perm_ids) + UserPermTreeUtil.add_need_refresh_by_asset_perm_ids_cross_orgs(asset_perm_ids) @register_as_period_task(crontab=CRONTAB_AT_AM_TEN) diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 4358e2075..89407be14 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -1,5 +1,8 @@ - +from django.db.models import QuerySet, Model +from collections.abc import Iterable +from assets.models import Node, Asset from common.utils import get_logger + from perms.models import AssetPermission logger = get_logger(__file__) @@ -41,14 +44,16 @@ class AssetPermissionUtil(object): perms = self.get_permissions(ids=group_perm_ids) return perms - def get_permissions_for_asset(self, asset, with_node=True, flat=False): + def get_permissions_for_assets(self, assets, with_node=True, flat=False): """ 获取资产的授权规则""" perm_ids = set() - asset_perm_ids = AssetPermission.assets.through.objects.filter(asset_id=asset.id) \ - .values_list('assetpermission_id', flat=True).distinct() + assets = self.transform_to_queryset(assets, Asset) + asset_ids = [str(a.id) for a in assets] + relations = AssetPermission.assets.through.objects.filter(asset_id__in=asset_ids) + asset_perm_ids = relations.values_list('assetpermission_id', flat=True).distinct() perm_ids.update(asset_perm_ids) if with_node: - nodes = asset.get_all_nodes() + nodes = Asset.get_all_nodes_for_assets(assets) node_perm_ids = self.get_permissions_for_nodes(nodes, flat=True) perm_ids.update(node_perm_ids) if flat: @@ -58,16 +63,12 @@ class AssetPermissionUtil(object): def get_permissions_for_nodes(self, nodes, with_ancestor=False, flat=False): """ 获取节点的授权规则 """ + nodes = self.transform_to_queryset(nodes, Node) if with_ancestor: - node_ids = set() - for node in nodes: - _nodes = node.get_ancestors(with_self=True) - _node_ids = _nodes.values_list('id', flat=True).distinct() - node_ids.update(_node_ids) - else: - node_ids = nodes.values_list('id', flat=True).distinct() - perm_ids = AssetPermission.nodes.through.objects.filter(node_id__in=node_ids) \ - .values_list('assetpermission_id', flat=True).distinct() + nodes = Node.get_ancestor_queryset(nodes) + node_ids = nodes.values_list('id', flat=True).distinct() + relations = AssetPermission.nodes.through.objects.filter(node_id__in=node_ids) + perm_ids = relations.values_list('assetpermission_id', flat=True).distinct() if flat: return perm_ids perms = self.get_permissions(ids=perm_ids) @@ -76,14 +77,14 @@ class AssetPermissionUtil(object): def get_permissions_for_user_asset(self, user, asset): """ 获取同时包含用户、资产的授权规则 """ user_perm_ids = self.get_permissions_for_user(user, flat=True) - asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) + asset_perm_ids = self.get_permissions_for_assets([asset], flat=True) perm_ids = set(user_perm_ids) & set(asset_perm_ids) perms = self.get_permissions(ids=perm_ids) return perms def get_permissions_for_user_group_asset(self, user_group, asset): user_perm_ids = self.get_permissions_for_user_groups([user_group], flat=True) - asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) + asset_perm_ids = self.get_permissions_for_assets([asset], flat=True) perm_ids = set(user_perm_ids) & set(asset_perm_ids) perms = self.get_permissions(ids=perm_ids) return perms @@ -92,3 +93,15 @@ class AssetPermissionUtil(object): def get_permissions(ids): perms = AssetPermission.objects.filter(id__in=ids).order_by('-date_expired') return perms + + @staticmethod + def transform_to_queryset(objs_or_ids, model): + if not objs_or_ids: + return objs_or_ids + if isinstance(objs_or_ids, QuerySet): + return objs_or_ids + ids = [str(o.id) if isinstance(o, model) else o for o in objs_or_ids] + return model.objects.filter(id__in=ids) + + + diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index 92a42a401..dc90e29bf 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -25,6 +25,7 @@ from perms.models import ( AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation ) from users.models import User +from .permission import AssetPermissionUtil NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id') @@ -32,32 +33,92 @@ NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id') logger = get_logger(__name__) -def get_user_all_asset_perm_ids(user) -> set: - asset_perm_ids = set() - user_perm_id = AssetPermission.users.through.objects \ - .filter(user_id=user.id) \ - .values_list('assetpermission_id', flat=True) \ - .distinct() - asset_perm_ids.update(user_perm_id) +class UserPermTreeUtil2: + cache_key_template = 'perms.user.node_tree.built_orgs.user_id:{user_id}' - group_ids = user.groups.through.objects \ - .filter(user_id=user.id) \ - .values_list('usergroup_id', flat=True) \ - .distinct() - group_ids = list(group_ids) - groups_perm_id = AssetPermission.user_groups.through.objects \ - .filter(usergroup_id__in=group_ids) \ - .values_list('assetpermission_id', flat=True) \ - .distinct() - asset_perm_ids.update(groups_perm_id) + def __init__(self, user): + self.user = user + self.orgs = self.user.orgs.distinct() + self.org_ids = [str(o.id) for o in self.orgs] - asset_perm_ids = AssetPermission.objects.filter( - id__in=asset_perm_ids).valid().values_list('id', flat=True) - asset_perm_ids = set(asset_perm_ids) - return asset_perm_ids + def get_cache_key(self, user_id): + return self.cache_key_template.format(user_id=user_id) + + @lazyproperty + def cache_key_user(self): + return self.get_cache_key(self.user.id) + + @lazyproperty + def cache_key_all_user(self): + return self.get_cache_key('*') + + @lazyproperty + def client(self): + return cache.client.get_client(write=True) + + @timeit + def refresh_if_need(self, force=False): + self.clean_user_perm_tree_nodes_for_legacy_org() + + to_refresh_orgs = self.orgs if force else self.get_user_need_refresh_orgs() + if not to_refresh_orgs: + logger.info('Not have to refresh orgs') + return + + with UserGrantedTreeRebuildLock(self.user.id): + for org in to_refresh_orgs: + with tmp_to_org(org): + start = time.time() + UserGrantedTreeBuildUtils(self.user).rebuild_user_granted_tree() + end = time.time() + logger.info( + 'Refresh user [{user}] org [{org}] perm tree, user {use_time:.2f}s' + ''.format(user=self.user, org=org, use_time=end-start) + ) + self.mark_user_orgs_refresh_finished(to_refresh_orgs) + + def clean_user_perm_tree_nodes_for_legacy_org(self): + with tmp_to_root_org(): + """ Clean user legacy org node relations """ + user_relations = UserAssetGrantedTreeNodeRelation.objects.filter(user=self.user) + user_legacy_org_relations = user_relations.exclude(org_id__in=self.org_ids) + user_legacy_org_relations.delete() + + def get_user_need_refresh_orgs(self): + cached_org_ids = self.client.smembers(self.cache_key_user) + cached_org_ids = {oid.decode() for oid in cached_org_ids} + to_refresh_org_ids = set(self.org_ids) - cached_org_ids + to_refresh_orgs = Organization.objects.filter(id__in=to_refresh_org_ids) + logger.info(f'Need to refresh orgs: {to_refresh_orgs}') + return to_refresh_orgs + + def mark_user_orgs_refresh_finished(self, org_ids): + self.client.sadd(self.cache_key_user, *org_ids) + + # cls + def expire_perm_tree_for_all_user(self): + keys = self.client.keys(self.cache_key_all_user) + with self.client.pipline() as p: + for k in keys: + p.delete(k) + p.execute() + + def expire_perm_tree_for_users_orgs(self, user_ids, org_ids): + org_ids = [str(oid) for oid in org_ids] + with self.client.pipline() as p: + for uid in user_ids: + cache_key = self.get_cache_key(uid) + p.srem(cache_key, *org_ids) + p.execute() + logger.info('Expire perm tree for users: [{}], orgs: [{}]'.format(user_ids, org_ids)) + + def expire_perm_tree_for_nodes_assets(self, node_ids, asset_ids): + node_perm_ids = AssetPermissionUtil().get_permissions_for_nodes(node_ids, flat=True) + asset_perm_ids = AssetPermissionUtil().get_permissions_for_assets(asset_ids, flat=True) + perm_ids = set(node_perm_ids) | set(asset_perm_ids) -class UserGrantedTreeRefreshController: +class UserPermTreeUtil: key_template = 'perms.user.node_tree.built_orgs.user_id:{user_id}' def __init__(self, user): @@ -65,24 +126,49 @@ class UserGrantedTreeRefreshController: self.key = self.key_template.format(user_id=user.id) self.client = self.get_redis_client() - @classmethod - def clean_all_user_tree_built_mark(cls): - """ 清除所有用户已构建树的标记 """ - client = cls.get_redis_client() - key_match = cls.key_template.format(user_id='*') - keys = client.keys(key_match) - with client.pipeline() as p: - for key in keys: - p.delete(key) - p.execute() + @timeit + def refresh_if_need(self, force=False): + user = self.user + orgs = user.orgs.all().distinct() + org_ids = [str(o.id) for o in orgs] - @classmethod - def get_redis_client(cls): - return cache.client.get_client(write=True) + with tmp_to_root_org(): + user_relations = UserAssetGrantedTreeNodeRelation.objects.filter(user=user) + user_legacy_org_relations = user_relations.exclude(org_id__in=org_ids) + user_legacy_org_relations.delete() - def get_need_refresh_org_ids(self): - org_ids = self.client.smembers(self.key) - return {org_id.decode() for org_id in org_ids} + need_refresh_orgs = [] + + if not force and not self.have_need_refresh_orgs(): + return + + with UserGrantedTreeRebuildLock(user_id=user.id): + if force: + orgs = self.orgs + self.set_all_orgs_as_built() + else: + orgs = self.get_need_refresh_orgs_and_fill_up() + + for org in orgs: + with tmp_to_org(org): + t_start = time.time() + logger.info(f'Rebuild user tree: user={self.user} org={current_org}') + utils = UserGrantedTreeBuildUtils(user) + utils.rebuild_user_granted_tree() + logger.info( + f'Rebuild user tree ok: cost={time.time() - t_start} ' + f'user={self.user} org={current_org}' + ) + + @lazyproperty + def org_ids(self): + ret = {str(org.id) for org in self.orgs} + return ret + + @lazyproperty + def orgs(self): + orgs = {*self.user.orgs.all().distinct()} + return orgs def set_all_orgs_as_built(self): self.client.sadd(self.key, *self.org_ids) @@ -99,15 +185,28 @@ class UserGrantedTreeRefreshController: with self.client.pipeline() as p: p.smembers(self.key) p.sadd(self.key, *org_ids) - ret = p.execute() - built_org_ids = {org_id.decode() for org_id in ret[0]} - ids = org_ids - built_org_ids - orgs = {*Organization.objects.filter(id__in=ids)} - logger.info( - f'Need rebuild orgs are {orgs}, built orgs are {ret[0]}, ' - f'all orgs are {org_ids}' - ) - return orgs + old_org_ids, new_orgs_count = p.execute() + old_org_ids = {oid.decode() for oid in old_org_ids} + need_refresh_org_ids = org_ids - old_org_ids + need_refresh_orgs = Organization.objects.filter(id__in=need_refresh_org_ids) + logger.info(f'Need refresh orgs: {need_refresh_orgs}') + return need_refresh_orgs + + # cls + @classmethod + def get_redis_client(cls): + return cache.client.get_client(write=True) + + @classmethod + def clean_all_user_tree_built_mark(cls): + """ 清除所有用户已构建树的标记 """ + client = cls.get_redis_client() + key_match = cls.key_template.format(user_id='*') + keys = client.keys(key_match) + with client.pipeline() as p: + for key in keys: + p.delete(key) + p.execute() @classmethod @on_transaction_commit @@ -190,46 +289,6 @@ class UserGrantedTreeRefreshController: [current_org.id], user_ids ) - @lazyproperty - def org_ids(self): - ret = {str(org.id) for org in self.orgs} - return ret - - @lazyproperty - def orgs(self): - orgs = {*self.user.orgs.all().distinct()} - return orgs - - @timeit - def refresh_if_need(self, force=False): - user = self.user - - with tmp_to_root_org(): - UserAssetGrantedTreeNodeRelation.objects.filter(user=user) \ - .exclude(org_id__in=self.org_ids) \ - .delete() - - if not force and not self.have_need_refresh_orgs(): - return - - with UserGrantedTreeRebuildLock(user_id=user.id): - if force: - orgs = self.orgs - self.set_all_orgs_as_built() - else: - orgs = self.get_need_refresh_orgs_and_fill_up() - - for org in orgs: - with tmp_to_org(org): - t_start = time.time() - logger.info(f'Rebuild user tree: user={self.user} org={current_org}') - utils = UserGrantedTreeBuildUtils(user) - utils.rebuild_user_granted_tree() - logger.info( - f'Rebuild user tree ok: cost={time.time() - t_start} ' - f'user={self.user} org={current_org}' - ) - class UserGrantedUtilsBase: user: User @@ -243,7 +302,7 @@ class UserGrantedUtilsBase: if self._asset_perm_ids: return self._asset_perm_ids - asset_perm_ids = get_user_all_asset_perm_ids(self.user) + asset_perm_ids = AssetPermissionUtil().get_permissions_for_user(self.user, flat=True) return asset_perm_ids diff --git a/apps/settings/signal_handlers.py b/apps/settings/signal_handlers.py index b04ae4f5e..0f4025c0a 100644 --- a/apps/settings/signal_handlers.py +++ b/apps/settings/signal_handlers.py @@ -38,8 +38,8 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): if instance.name == 'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': # 清除所有用户授权树已构建的标记,下次访问重新生成 logger.debug('Clean ALL User perm tree built mark') - from perms.utils.asset import UserGrantedTreeRefreshController - UserGrantedTreeRefreshController.clean_all_user_tree_built_mark() + from perms.utils.user_permission import UserPermTreeUtil + UserPermTreeUtil.clean_all_user_tree_built_mark() @receiver(django_ready) From 1679efe2c973e578e540493d219ba8392ef1434f Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 9 Dec 2022 13:05:34 +0800 Subject: [PATCH 020/132] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=94=A8=E6=88=B7=E6=8E=88=E6=9D=83=E6=A0=91=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=B1=BB=E5=92=8C=E7=94=A8=E6=88=B7=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E6=A0=91=E8=BF=87=E6=9C=9F=E6=9D=A1=E4=BB=B6=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/tree/mixin.py | 5 +- apps/perms/models/asset_permission.py | 29 +- apps/perms/signal_handlers/refresh_perms.py | 91 +++--- apps/perms/tasks.py | 46 +-- apps/perms/utils/user_permission.py | 289 +++++-------------- apps/settings/models.py | 3 + apps/settings/signal_handlers.py | 17 +- apps/users/models/group.py | 4 +- 8 files changed, 167 insertions(+), 317 deletions(-) diff --git a/apps/perms/api/user_permission/tree/mixin.py b/apps/perms/api/user_permission/tree/mixin.py index 87c4eba3c..b5f9cc5b6 100644 --- a/apps/perms/api/user_permission/tree/mixin.py +++ b/apps/perms/api/user_permission/tree/mixin.py @@ -1,9 +1,10 @@ from rest_framework.request import Request from users.models import User -from perms.utils.user_permission import UserPermTreeUtil from common.http import is_true +from perms.utils.user_permission import UserPermTreeRefreshUtil + __all__ = ['RebuildTreeMixin'] @@ -13,5 +14,5 @@ class RebuildTreeMixin: def get(self, request: Request, *args, **kwargs): force = is_true(request.query_params.get('rebuild_tree')) - UserPermTreeUtil(self.user).refresh_if_need(force) + UserPermTreeRefreshUtil(self.user).refresh_if_need(force) return super().get(request, *args, **kwargs) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index aa61ffe14..2277cafa6 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -1,16 +1,19 @@ -import logging import uuid +import logging from django.db import models from django.db.models import Q from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from users.models import User from assets.models import Asset, Account -from common.db.models import UnionQuerySet -from common.utils import date_expired_default from orgs.mixins.models import OrgManager from orgs.mixins.models import OrgModelMixin +from common.utils.timezone import local_now +from common.db.models import UnionQuerySet +from common.utils import date_expired_default + from perms.const import ActionChoices __all__ = ['AssetPermission', 'ActionChoices'] @@ -131,3 +134,23 @@ class AssetPermission(OrgModelMixin): if not flat: return accounts return accounts.values_list('id', flat=True) + + @classmethod + def get_all_users_for_perms(cls, perm_ids, flat=False): + user_ids = cls.users.through.objects.filter(assetpermission_id__in=perm_ids)\ + .values_list('user_id', flat=True).distinct() + group_ids = cls.user_groups.through.objects.filter(assetpermission_id__in=perm_ids)\ + .values_list('usergroup_id', flat=True).distinct() + group_user_ids = User.groups.through.objects.filter(usergroup_id__in=group_ids)\ + .values_list('user_id', flat=True).distinct() + user_ids = set(user_ids) | set(group_user_ids) + if flat: + return user_ids + return User.objects.filter(id__in=user_ids) + + @classmethod + def get_expired_permissions(cls): + now = local_now() + return cls.objects.filter(Q(date_start__lte=now) | Q(date_expired__gte=now)) + + diff --git a/apps/perms/signal_handlers/refresh_perms.py b/apps/perms/signal_handlers/refresh_perms.py index 2d5617b34..0f37aee79 100644 --- a/apps/perms/signal_handlers/refresh_perms.py +++ b/apps/perms/signal_handlers/refresh_perms.py @@ -5,12 +5,12 @@ from django.dispatch import receiver from users.models import User, UserGroup from assets.models import Asset -from orgs.utils import current_org, tmp_to_org -from common.utils import get_logger +from common.utils import get_logger, get_object_or_none from common.exceptions import M2MReverseNotAllowed from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR + from perms.models import AssetPermission -from perms.utils.user_permission import UserPermTreeUtil +from perms.utils.user_permission import UserPermTreeExpireUtil logger = get_logger(__file__) @@ -21,10 +21,7 @@ def on_user_group_delete(sender, instance: UserGroup, using, **kwargs): exists = AssetPermission.user_groups.through.objects.filter(usergroup_id=instance.id).exists() if not exists: return - - org_id = instance.org_id - user_ids = UserGroup.users.through.objects.filter(usergroup_id=instance.id).values_list('user_id', flat=True) - UserPermTreeUtil.add_need_refresh_orgs_for_users([org_id], list(user_ids)) + UserPermTreeExpireUtil().expire_perm_tree_for_user_group(instance) @receiver(m2m_changed, sender=User.groups.through) @@ -41,39 +38,34 @@ def on_user_groups_change(sender, instance, action, reverse, pk_set, **kwargs): group = UserGroup.objects.get(id=list(group_ids)[0]) org_id = group.org_id - exists = AssetPermission.user_groups.through.objects.filter(usergroup_id__in=group_ids).exists() - if not exists: + has_group_perm = AssetPermission.user_groups.through.objects\ + .filter(usergroup_id__in=group_ids).exists() + if not has_group_perm: return - org_ids = [org_id] - UserPermTreeUtil.add_need_refresh_orgs_for_users(org_ids, user_ids) + UserPermTreeExpireUtil().expire_perm_tree_for_users_orgs(user_ids, [org_id]) @receiver([pre_delete], sender=AssetPermission) def on_asset_perm_pre_delete(sender, instance, **kwargs): - # 授权删除之前,查出所有相关用户 - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) + UserPermTreeExpireUtil().expire_perm_tree_for_perms([instance.id]) @receiver([pre_save], sender=AssetPermission) def on_asset_perm_pre_save(sender, instance, **kwargs): - try: - old = AssetPermission.objects.get(id=instance.id) - - if old.is_valid != instance.is_valid: - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) - except AssetPermission.DoesNotExist: - pass + old = get_object_or_none(AssetPermission, pk=instance.id) + if not old: + return + if old.is_valid == instance.is_valid: + return + UserPermTreeExpireUtil().expire_perm_tree_for_perms([instance.id]) @receiver([post_save], sender=AssetPermission) def on_asset_perm_post_save(sender, instance, created, **kwargs): if not created: return - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) + UserPermTreeExpireUtil().expire_perm_tree_for_perms([instance.id]) def need_rebuild_mapping_node(action): @@ -82,69 +74,52 @@ def need_rebuild_mapping_node(action): @receiver(m2m_changed, sender=AssetPermission.nodes.through) def on_permission_nodes_changed(sender, instance, action, reverse, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if not need_rebuild_mapping_node(action): return - - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) + if reverse: + raise M2MReverseNotAllowed + UserPermTreeExpireUtil().expire_perm_tree_for_perms([instance.id]) @receiver(m2m_changed, sender=AssetPermission.assets.through) def on_permission_assets_changed(sender, instance, action, reverse, pk_set, model, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if not need_rebuild_mapping_node(action): return - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_by_asset_perm_ids([instance.id]) + if reverse: + raise M2MReverseNotAllowed + UserPermTreeExpireUtil().expire_perm_tree_for_perms([instance.id]) @receiver(m2m_changed, sender=AssetPermission.users.through) def on_asset_permission_users_changed(sender, action, reverse, instance, pk_set, **kwargs): if reverse: raise M2MReverseNotAllowed - if not need_rebuild_mapping_node(action): return - - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_orgs_for_users( - [current_org.id], pk_set - ) + user_ids = pk_set + UserPermTreeExpireUtil().expire_perm_tree_for_users_orgs(user_ids, [instance.org.id]) @receiver(m2m_changed, sender=AssetPermission.user_groups.through) def on_asset_permission_user_groups_changed(sender, instance, action, pk_set, reverse, **kwargs): + if not need_rebuild_mapping_node(action): + return if reverse: raise M2MReverseNotAllowed - if not need_rebuild_mapping_node(action): - return - - user_ids = User.groups.through.objects.filter(usergroup_id__in=pk_set) \ - .values_list('user_id', flat=True) \ - .distinct() - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_orgs_for_users( - [current_org.id], user_ids - ) + group_ids = pk_set + UserPermTreeExpireUtil().expire_perm_tree_for_user_groups_orgs(group_ids, [instance.org.id]) @receiver(m2m_changed, sender=Asset.nodes.through) def on_node_asset_change(action, instance, reverse, pk_set, **kwargs): if not need_rebuild_mapping_node(action): return - if reverse: - asset_pk_set = pk_set - node_pk_set = [instance.id] + asset_ids = pk_set + node_ids = [instance.id] else: - asset_pk_set = [instance.id] - node_pk_set = pk_set + asset_ids = [instance.id] + node_ids = pk_set - with tmp_to_org(instance.org): - UserPermTreeUtil.add_need_refresh_on_nodes_assets_relate_change(node_pk_set, asset_pk_set) + UserPermTreeExpireUtil().expire_perm_tree_for_nodes_assets(node_ids, asset_ids) diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index e739f4bcd..781feffbd 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -7,16 +7,18 @@ from django.db.transaction import atomic from django.conf import settings from celery import shared_task +from ops.celery.decorator import register_as_period_task from orgs.utils import tmp_to_root_org from common.utils import get_logger -from common.utils.timezone import local_now, dt_formatter, dt_parser +from common.utils.timezone import local_now, dt_parser from common.const.crontab import CRONTAB_AT_AM_TEN -from ops.celery.decorator import register_as_period_task -from perms.notifications import ( - PermedAssetsWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg, -) + from perms.models import AssetPermission -from perms.utils.user_permission import UserPermTreeUtil +from perms.utils.user_permission import UserPermTreeExpireUtil +from perms.notifications import ( + PermedAssetsWillExpireUserMsg, + AssetPermsWillExpireForOrgAdminMsg, +) logger = get_logger(__file__) @@ -26,33 +28,11 @@ logger = get_logger(__file__) @atomic() @tmp_to_root_org() def check_asset_permission_expired(): - """ - 这里的任务要足够短,不要影响周期任务 - """ - from settings.models import Setting - - setting_name = 'last_asset_perm_expired_check' - - end = local_now() - default_start = end - timedelta(days=36000) # Long long ago in china - - defaults = {'value': dt_formatter(default_start)} - setting, created = Setting.objects.get_or_create( - name=setting_name, defaults=defaults - ) - if created: - start = default_start - else: - start = dt_parser(setting.value) - setting.value = dt_formatter(end) - setting.save() - - asset_perm_ids = AssetPermission.objects.filter( - date_expired__gte=start, date_expired__lte=end - ).distinct().values_list('id', flat=True) - asset_perm_ids = list(asset_perm_ids) - logger.info(f'>>> checking {start} to {end} have {asset_perm_ids} expired') - UserPermTreeUtil.add_need_refresh_by_asset_perm_ids_cross_orgs(asset_perm_ids) + """ 这里的任务要足够短,不要影响周期任务 """ + perms = AssetPermission.get_expired_permissions() + perm_ids = list(perms.distinct().values_list('id', flat=True)) + logger.info(f'Checking expired permissions: {perm_ids}') + UserPermTreeExpireUtil().expire_perm_tree_for_perms(perm_ids) @register_as_period_task(crontab=CRONTAB_AT_AM_TEN) diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index dc90e29bf..32d3ffeb2 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -7,24 +7,32 @@ from django.core.cache import cache from django.db.models import Q, QuerySet from django.utils.translation import gettext as _ -from assets.models import ( - Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet -) +from users.models import User from assets.utils import NodeAssetsUtil +from assets.models import ( + Asset, + FavoriteAsset, + AssetQuerySet, + NodeQuerySet +) +from orgs.models import Organization +from orgs.utils import ( + tmp_to_org, + current_org, + ensure_in_real_or_default_org, + tmp_to_root_org +) from common.db.models import output_as_string, UnionQuerySet from common.decorator import on_transaction_commit from common.utils import get_logger from common.utils.common import lazyproperty, timeit -from orgs.models import Organization -from orgs.utils import ( - tmp_to_org, current_org, - ensure_in_real_or_default_org, tmp_to_root_org -) + from perms.locks import UserGrantedTreeRebuildLock from perms.models import ( - AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation + AssetPermission, + PermNode, + UserAssetGrantedTreeNodeRelation ) -from users.models import User from .permission import AssetPermissionUtil NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom @@ -33,50 +41,53 @@ NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id') logger = get_logger(__name__) -class UserPermTreeUtil2: +class UserPermTreeCacheMixin: + """ 缓存数据 users: {org_id, org_id }, 记录用户授权树已经构建完成的组织集合 """ + cache_key_template = 'perms.user.node_tree.built_orgs.user_id:{user_id}' + def get_cache_key(self, user_id): + return self.cache_key_template.format(user_id=user_id) + + @lazyproperty + def client(self): + return cache.client.get_client(write=True) + + +class UserPermTreeRefreshUtil(UserPermTreeCacheMixin): + """ 用户授权树刷新工具, 针对某一个用户授权树的刷新 """ + def __init__(self, user): self.user = user self.orgs = self.user.orgs.distinct() self.org_ids = [str(o.id) for o in self.orgs] - def get_cache_key(self, user_id): - return self.cache_key_template.format(user_id=user_id) - @lazyproperty def cache_key_user(self): return self.get_cache_key(self.user.id) - @lazyproperty - def cache_key_all_user(self): - return self.get_cache_key('*') - - @lazyproperty - def client(self): - return cache.client.get_client(write=True) - @timeit def refresh_if_need(self, force=False): self.clean_user_perm_tree_nodes_for_legacy_org() - to_refresh_orgs = self.orgs if force else self.get_user_need_refresh_orgs() if not to_refresh_orgs: logger.info('Not have to refresh orgs') return - with UserGrantedTreeRebuildLock(self.user.id): for org in to_refresh_orgs: - with tmp_to_org(org): - start = time.time() - UserGrantedTreeBuildUtils(self.user).rebuild_user_granted_tree() - end = time.time() - logger.info( - 'Refresh user [{user}] org [{org}] perm tree, user {use_time:.2f}s' - ''.format(user=self.user, org=org, use_time=end-start) - ) + self.rebuild_user_perm_tree_for_org(org) self.mark_user_orgs_refresh_finished(to_refresh_orgs) + def rebuild_user_perm_tree_for_org(self, org): + with tmp_to_org(org): + start = time.time() + UserGrantedTreeBuildUtils(self.user).rebuild_user_granted_tree() + end = time.time() + logger.info( + 'Refresh user [{user}] org [{org}] perm tree, user {use_time:.2f}s' + ''.format(user=self.user, org=org, use_time=end-start) + ) + def clean_user_perm_tree_nodes_for_legacy_org(self): with tmp_to_root_org(): """ Clean user legacy org node relations """ @@ -95,7 +106,14 @@ class UserPermTreeUtil2: def mark_user_orgs_refresh_finished(self, org_ids): self.client.sadd(self.cache_key_user, *org_ids) - # cls + +class UserPermTreeExpireUtil(UserPermTreeCacheMixin): + """ 用户授权树过期工具 """ + + @lazyproperty + def cache_key_all_user(self): + return self.get_cache_key('*') + def expire_perm_tree_for_all_user(self): keys = self.client.keys(self.cache_key_all_user) with self.client.pipline() as p: @@ -103,6 +121,34 @@ class UserPermTreeUtil2: p.delete(k) p.execute() + def expire_perm_tree_for_nodes_assets(self, node_ids, asset_ids): + node_perm_ids = AssetPermissionUtil().get_permissions_for_nodes(node_ids, flat=True) + asset_perm_ids = AssetPermissionUtil().get_permissions_for_assets(asset_ids, flat=True) + perm_ids = set(node_perm_ids) | set(asset_perm_ids) + self.expire_perm_tree_for_perms(perm_ids) + + @tmp_to_root_org() + def expire_perm_tree_for_perms(self, perm_ids): + org_perm_ids = AssetPermission.objects.filter(id__in=perm_ids).values_list('org_id', 'id') + org_perms_mapper = defaultdict(set) + for org_id, perm_id in org_perm_ids: + org_perms_mapper[org_id].add(perm_id) + for org_id, perms_id in org_perms_mapper.items(): + org_ids = [org_id] + user_ids = AssetPermission.get_all_users_for_perms(perm_ids, flat=True) + self.expire_perm_tree_for_users_orgs(user_ids, org_ids) + + def expire_perm_tree_for_user_group(self, user_group): + group_ids = [user_group.id] + org_ids = [user_group.org_id] + self.expire_perm_tree_for_user_groups_orgs(group_ids, org_ids) + + def expire_perm_tree_for_user_groups_orgs(self, group_ids, org_ids): + user_ids = User.groups.through.objects.filter(usergroup_id__in=group_ids)\ + .values_list('user_id', flat=True).distinct() + self.expire_perm_tree_for_users_orgs(user_ids, org_ids) + + @on_transaction_commit def expire_perm_tree_for_users_orgs(self, user_ids, org_ids): org_ids = [str(oid) for oid in org_ids] with self.client.pipline() as p: @@ -112,183 +158,6 @@ class UserPermTreeUtil2: p.execute() logger.info('Expire perm tree for users: [{}], orgs: [{}]'.format(user_ids, org_ids)) - def expire_perm_tree_for_nodes_assets(self, node_ids, asset_ids): - node_perm_ids = AssetPermissionUtil().get_permissions_for_nodes(node_ids, flat=True) - asset_perm_ids = AssetPermissionUtil().get_permissions_for_assets(asset_ids, flat=True) - perm_ids = set(node_perm_ids) | set(asset_perm_ids) - - -class UserPermTreeUtil: - key_template = 'perms.user.node_tree.built_orgs.user_id:{user_id}' - - def __init__(self, user): - self.user = user - self.key = self.key_template.format(user_id=user.id) - self.client = self.get_redis_client() - - @timeit - def refresh_if_need(self, force=False): - user = self.user - orgs = user.orgs.all().distinct() - org_ids = [str(o.id) for o in orgs] - - with tmp_to_root_org(): - user_relations = UserAssetGrantedTreeNodeRelation.objects.filter(user=user) - user_legacy_org_relations = user_relations.exclude(org_id__in=org_ids) - user_legacy_org_relations.delete() - - need_refresh_orgs = [] - - if not force and not self.have_need_refresh_orgs(): - return - - with UserGrantedTreeRebuildLock(user_id=user.id): - if force: - orgs = self.orgs - self.set_all_orgs_as_built() - else: - orgs = self.get_need_refresh_orgs_and_fill_up() - - for org in orgs: - with tmp_to_org(org): - t_start = time.time() - logger.info(f'Rebuild user tree: user={self.user} org={current_org}') - utils = UserGrantedTreeBuildUtils(user) - utils.rebuild_user_granted_tree() - logger.info( - f'Rebuild user tree ok: cost={time.time() - t_start} ' - f'user={self.user} org={current_org}' - ) - - @lazyproperty - def org_ids(self): - ret = {str(org.id) for org in self.orgs} - return ret - - @lazyproperty - def orgs(self): - orgs = {*self.user.orgs.all().distinct()} - return orgs - - def set_all_orgs_as_built(self): - self.client.sadd(self.key, *self.org_ids) - - def have_need_refresh_orgs(self): - built_org_ids = self.client.smembers(self.key) - built_org_ids = {org_id.decode() for org_id in built_org_ids} - have = self.org_ids - built_org_ids - return have - - def get_need_refresh_orgs_and_fill_up(self): - org_ids = self.org_ids - - with self.client.pipeline() as p: - p.smembers(self.key) - p.sadd(self.key, *org_ids) - old_org_ids, new_orgs_count = p.execute() - old_org_ids = {oid.decode() for oid in old_org_ids} - need_refresh_org_ids = org_ids - old_org_ids - need_refresh_orgs = Organization.objects.filter(id__in=need_refresh_org_ids) - logger.info(f'Need refresh orgs: {need_refresh_orgs}') - return need_refresh_orgs - - # cls - @classmethod - def get_redis_client(cls): - return cache.client.get_client(write=True) - - @classmethod - def clean_all_user_tree_built_mark(cls): - """ 清除所有用户已构建树的标记 """ - client = cls.get_redis_client() - key_match = cls.key_template.format(user_id='*') - keys = client.keys(key_match) - with client.pipeline() as p: - for key in keys: - p.delete(key) - p.execute() - - @classmethod - @on_transaction_commit - def remove_built_orgs_from_users(cls, org_ids, user_ids): - client = cls.get_redis_client() - org_ids = [str(org_id) for org_id in org_ids] - - with client.pipeline() as p: - for user_id in user_ids: - key = cls.key_template.format(user_id=user_id) - p.srem(key, *org_ids) - p.execute() - logger.info(f'Remove orgs from users built tree: users:{user_ids} orgs:{org_ids}') - - @classmethod - def add_need_refresh_orgs_for_users(cls, org_ids, user_ids): - cls.remove_built_orgs_from_users(org_ids, user_ids) - - @classmethod - @ensure_in_real_or_default_org - def add_need_refresh_on_nodes_assets_relate_change(cls, node_ids, asset_ids): - """ - 1,计算与这些资产有关的授权 - 2,计算与这些节点以及祖先节点有关的授权 - """ - - node_ids = set(node_ids) - ancestor_node_keys = set() - asset_perm_ids = set() - - nodes = PermNode.objects.filter(id__in=node_ids).only('id', 'key') - for node in nodes: - ancestor_node_keys.update(node.get_ancestor_keys()) - - ancestor_id = PermNode.objects.filter(key__in=ancestor_node_keys).values_list('id', flat=True) - node_ids.update(ancestor_id) - - assets_related_perm_ids = AssetPermission.nodes.through.objects.filter( - node_id__in=node_ids - ).values_list('assetpermission_id', flat=True) - asset_perm_ids.update(assets_related_perm_ids) - - nodes_related_perm_ids = AssetPermission.assets.through.objects.filter( - asset_id__in=asset_ids - ).values_list('assetpermission_id', flat=True) - asset_perm_ids.update(nodes_related_perm_ids) - - cls.add_need_refresh_by_asset_perm_ids(asset_perm_ids) - - @classmethod - def add_need_refresh_by_asset_perm_ids_cross_orgs(cls, asset_perm_ids): - org_id_perm_ids_mapper = defaultdict(set) - pairs = AssetPermission.objects.filter(id__in=asset_perm_ids).values_list('org_id', 'id') - for org_id, perm_id in pairs: - org_id_perm_ids_mapper[org_id].add(perm_id) - for org_id, perm_ids in org_id_perm_ids_mapper.items(): - with tmp_to_org(org_id): - cls.add_need_refresh_by_asset_perm_ids(perm_ids) - - @classmethod - @ensure_in_real_or_default_org - def add_need_refresh_by_asset_perm_ids(cls, asset_perm_ids): - - group_ids = AssetPermission.user_groups.through.objects.filter( - assetpermission_id__in=asset_perm_ids - ).values_list('usergroup_id', flat=True) - - user_ids = set() - direct_user_id = AssetPermission.users.through.objects.filter( - assetpermission_id__in=asset_perm_ids - ).values_list('user_id', flat=True) - user_ids.update(direct_user_id) - - group_user_ids = User.groups.through.objects.filter( - usergroup_id__in=group_ids - ).values_list('user_id', flat=True) - user_ids.update(group_user_ids) - - cls.remove_built_orgs_from_users( - [current_org.id], user_ids - ) - class UserGrantedUtilsBase: user: User diff --git a/apps/settings/models.py b/apps/settings/models.py index 8506a0fa1..170d032a4 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -43,6 +43,9 @@ class Setting(models.Model): def __str__(self): return self.name + def is_name(self, name): + return self.name == name + @property def cleaned_value(self): try: diff --git a/apps/settings/signal_handlers.py b/apps/settings/signal_handlers.py index 0f4025c0a..58447ba79 100644 --- a/apps/settings/signal_handlers.py +++ b/apps/settings/signal_handlers.py @@ -8,11 +8,13 @@ from django.db.utils import ProgrammingError, OperationalError from django.dispatch import receiver from django.utils.functional import LazyObject +from jumpserver.utils import current_request + from common.decorator import on_transaction_commit from common.signals import django_ready from common.utils import get_logger, ssh_key_gen from common.utils.connection import RedisPubSub -from jumpserver.utils import current_request + from .models import Setting logger = get_logger(__file__) @@ -31,15 +33,12 @@ setting_pub_sub = SettingSubPub() def refresh_settings_on_changed(sender, instance=None, **kwargs): if not instance: return - setting_pub_sub.publish(instance.name) - - # 配置变化: PERM_SINGLE_ASSET_TO_UNGROUP_NODE - if instance.name == 'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': - # 清除所有用户授权树已构建的标记,下次访问重新生成 - logger.debug('Clean ALL User perm tree built mark') - from perms.utils.user_permission import UserPermTreeUtil - UserPermTreeUtil.clean_all_user_tree_built_mark() + if instance.is_name('PERM_SINGLE_ASSET_TO_UNGROUP_NODE'): + """ 过期所有用户授权树 """ + logger.debug('Expire all user perm tree') + from perms.utils.user_permission import UserPermTreeExpireUtil + UserPermTreeExpireUtil().expire_perm_tree_for_all_user() @receiver(django_ready) diff --git a/apps/users/models/group.py b/apps/users/models/group.py index d06fce989..2f50d24a0 100644 --- a/apps/users/models/group.py +++ b/apps/users/models/group.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- import uuid -from django.db import models, IntegrityError +from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin +from common.utils import lazyproperty __all__ = ['UserGroup'] From a767c208b39ecce1f2e106bdec069646f61afacb Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 9 Dec 2022 13:13:02 +0800 Subject: [PATCH 021/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20get=5Fall?= =?UTF-8?q?=5Fnode=5Fkeys=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset/common.py | 18 ++++++++++-------- apps/assets/models/node.py | 4 +--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index c191dafe4..e7d3ae8db 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -63,26 +63,28 @@ class NodesRelationMixin: nodes = Node.objects.filter(id=Node.org_root().id) return nodes - def get_all_nodes(self, flat=False, only_keys=False): + def get_all_nodes(self, flat=False): from ..node import Node - node_keys = set() - for node in self.get_nodes(): - ancestor_keys = node.get_ancestor_keys(with_self=True) - node_keys.update(ancestor_keys) - if only_keys: - return node_keys + node_keys = self.get_all_node_keys() nodes = Node.objects.filter(key__in=node_keys).distinct() if not flat: return nodes node_ids = set(nodes.values_list('id', flat=True)) return node_ids + def get_all_node_keys(self): + node_keys = set() + for node in self.get_nodes(): + ancestor_keys = node.get_ancestor_keys(with_self=True) + node_keys.update(ancestor_keys) + return node_keys + @classmethod def get_all_nodes_for_assets(cls, assets): from ..node import Node node_keys = set() for asset in assets: - asset_node_keys = asset.get_all_nodes(only_keys=True) + asset_node_keys = asset.get_all_node_keys() node_keys.update(asset_node_keys) nodes = Node.objects.filter(key__in=node_keys) return nodes diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 54bdecf61..960d858d5 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -178,9 +178,7 @@ class FamilyMixin: return parent_keys def get_ancestor_keys(self, with_self=False): - return self.get_node_ancestor_keys( - self.key, with_self=with_self - ) + return self.get_node_ancestor_keys(self.key, with_self=with_self) @property def ancestors(self): From 89f89532e78441c3d1c5d029b17118ddcd9d0cc8 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 9 Dec 2022 13:26:29 +0800 Subject: [PATCH 022/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20convert=5F?= =?UTF-8?q?to=5Fqueryset=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/permission.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 89407be14..849bec02b 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -1,3 +1,4 @@ +import django from django.db.models import QuerySet, Model from collections.abc import Iterable from assets.models import Node, Asset @@ -36,18 +37,18 @@ class AssetPermissionUtil(object): group_ids = [g.id for g in user_groups] else: group_ids = user_groups.values_list('id', flat=True).distinct() - group_perm_ids = AssetPermission.user_groups.through.objects \ + perm_ids = AssetPermission.user_groups.through.objects \ .filter(usergroup_id__in=group_ids) \ .values_list('assetpermission_id', flat=True).distinct() if flat: - return group_perm_ids - perms = self.get_permissions(ids=group_perm_ids) + return perm_ids + perms = self.get_permissions(ids=perm_ids) return perms def get_permissions_for_assets(self, assets, with_node=True, flat=False): """ 获取资产的授权规则""" perm_ids = set() - assets = self.transform_to_queryset(assets, Asset) + assets = self.convert_to_queryset_if_need(assets, Asset) asset_ids = [str(a.id) for a in assets] relations = AssetPermission.assets.through.objects.filter(asset_id__in=asset_ids) asset_perm_ids = relations.values_list('assetpermission_id', flat=True).distinct() @@ -63,7 +64,7 @@ class AssetPermissionUtil(object): def get_permissions_for_nodes(self, nodes, with_ancestor=False, flat=False): """ 获取节点的授权规则 """ - nodes = self.transform_to_queryset(nodes, Node) + nodes = self.convert_to_queryset_if_need(nodes, Node) if with_ancestor: nodes = Node.get_ancestor_queryset(nodes) node_ids = nodes.values_list('id', flat=True).distinct() @@ -95,12 +96,15 @@ class AssetPermissionUtil(object): return perms @staticmethod - def transform_to_queryset(objs_or_ids, model): + def convert_to_queryset_if_need(objs_or_ids, model): if not objs_or_ids: return objs_or_ids - if isinstance(objs_or_ids, QuerySet): + if isinstance(objs_or_ids, QuerySet) and isinstance(objs_or_ids.first(), model): return objs_or_ids - ids = [str(o.id) if isinstance(o, model) else o for o in objs_or_ids] + ids = [ + str(i.id) if isinstance(i, model) else i + for i in objs_or_ids + ] return model.objects.filter(id__in=ids) From 5e5061a825df1b37395b733afbb0f564e9c50fca Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 9 Dec 2022 13:37:49 +0800 Subject: [PATCH 023/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20UserPermTr?= =?UTF-8?q?eeExprireUtil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/user_permission.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index 32d3ffeb2..9ba517125 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -114,13 +114,6 @@ class UserPermTreeExpireUtil(UserPermTreeCacheMixin): def cache_key_all_user(self): return self.get_cache_key('*') - def expire_perm_tree_for_all_user(self): - keys = self.client.keys(self.cache_key_all_user) - with self.client.pipline() as p: - for k in keys: - p.delete(k) - p.execute() - def expire_perm_tree_for_nodes_assets(self, node_ids, asset_ids): node_perm_ids = AssetPermissionUtil().get_permissions_for_nodes(node_ids, flat=True) asset_perm_ids = AssetPermissionUtil().get_permissions_for_assets(asset_ids, flat=True) @@ -158,6 +151,14 @@ class UserPermTreeExpireUtil(UserPermTreeCacheMixin): p.execute() logger.info('Expire perm tree for users: [{}], orgs: [{}]'.format(user_ids, org_ids)) + def expire_perm_tree_for_all_user(self): + keys = self.client.keys(self.cache_key_all_user) + with self.client.pipline() as p: + for k in keys: + p.delete(k) + p.execute() + logger.info('Expire all user perm tree') + class UserGrantedUtilsBase: user: User From 26d9cdc50d8eef7d34e0666118bb59a2ef4e2189 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 9 Dec 2022 14:03:00 +0800 Subject: [PATCH 024/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E6=A0=91=E5=B7=A5=E5=85=B7=E7=B1=BB?= =?UTF-8?q?=E5=AD=98=E6=94=BE=E7=9B=AE=E5=BD=95=20user=5Fperm=5Ftree=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/tree/mixin.py | 2 +- apps/perms/signal_handlers/refresh_perms.py | 2 +- apps/perms/tasks.py | 2 +- apps/perms/utils/__init__.py | 1 + apps/perms/utils/user_perm_tree.py | 145 +++++++++++++++++++ apps/perms/utils/user_permission.py | 125 ---------------- apps/settings/signal_handlers.py | 2 +- 7 files changed, 150 insertions(+), 129 deletions(-) create mode 100644 apps/perms/utils/user_perm_tree.py diff --git a/apps/perms/api/user_permission/tree/mixin.py b/apps/perms/api/user_permission/tree/mixin.py index b5f9cc5b6..d78a29b04 100644 --- a/apps/perms/api/user_permission/tree/mixin.py +++ b/apps/perms/api/user_permission/tree/mixin.py @@ -3,7 +3,7 @@ from rest_framework.request import Request from users.models import User from common.http import is_true -from perms.utils.user_permission import UserPermTreeRefreshUtil +from perms.utils import UserPermTreeRefreshUtil __all__ = ['RebuildTreeMixin'] diff --git a/apps/perms/signal_handlers/refresh_perms.py b/apps/perms/signal_handlers/refresh_perms.py index 0f37aee79..88a5fe674 100644 --- a/apps/perms/signal_handlers/refresh_perms.py +++ b/apps/perms/signal_handlers/refresh_perms.py @@ -10,7 +10,7 @@ from common.exceptions import M2MReverseNotAllowed from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR from perms.models import AssetPermission -from perms.utils.user_permission import UserPermTreeExpireUtil +from perms.utils import UserPermTreeExpireUtil logger = get_logger(__file__) diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 781feffbd..3df7a1728 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -14,7 +14,7 @@ from common.utils.timezone import local_now, dt_parser from common.const.crontab import CRONTAB_AT_AM_TEN from perms.models import AssetPermission -from perms.utils.user_permission import UserPermTreeExpireUtil +from perms.utils import UserPermTreeExpireUtil from perms.notifications import ( PermedAssetsWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg, diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py index fc2a94e88..8563b21e9 100644 --- a/apps/perms/utils/__init__.py +++ b/apps/perms/utils/__init__.py @@ -1,3 +1,4 @@ from .permission import * from .user_permission import * from .account import * +from .user_perm_tree import * diff --git a/apps/perms/utils/user_perm_tree.py b/apps/perms/utils/user_perm_tree.py new file mode 100644 index 000000000..d45b6a325 --- /dev/null +++ b/apps/perms/utils/user_perm_tree.py @@ -0,0 +1,145 @@ +import time +from collections import defaultdict + +from django.core.cache import cache + +from users.models import User +from orgs.models import Organization +from orgs.utils import ( + tmp_to_org, + tmp_to_root_org +) +from common.decorator import on_transaction_commit +from common.utils import get_logger +from common.utils.common import lazyproperty, timeit + +from perms.locks import UserGrantedTreeRebuildLock +from perms.models import ( + AssetPermission, + UserAssetGrantedTreeNodeRelation +) +from .permission import AssetPermissionUtil + + +logger = get_logger(__name__) + +__all__ = ['UserPermTreeRefreshUtil', 'UserPermTreeExpireUtil'] + + +class _UserPermTreeCacheMixin: + """ 缓存数据 users: {org_id, org_id }, 记录用户授权树已经构建完成的组织集合 """ + cache_key_template = 'perms.user.node_tree.built_orgs.user_id:{user_id}' + + def get_cache_key(self, user_id): + return self.cache_key_template.format(user_id=user_id) + + @lazyproperty + def client(self): + return cache.client.get_client(write=True) + + +class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): + """ 用户授权树刷新工具, 针对某一个用户授权树的刷新 """ + + def __init__(self, user): + self.user = user + self.orgs = self.user.orgs.distinct() + self.org_ids = [str(o.id) for o in self.orgs] + + @lazyproperty + def cache_key_user(self): + return self.get_cache_key(self.user.id) + + @timeit + def refresh_if_need(self, force=False): + self.clean_user_perm_tree_nodes_for_legacy_org() + to_refresh_orgs = self.orgs if force else self.get_user_need_refresh_orgs() + if not to_refresh_orgs: + logger.info('Not have to refresh orgs') + return + with UserGrantedTreeRebuildLock(self.user.id): + for org in to_refresh_orgs: + self.rebuild_user_perm_tree_for_org(org) + self.mark_user_orgs_refresh_finished(to_refresh_orgs) + + def rebuild_user_perm_tree_for_org(self, org): + with tmp_to_org(org): + start = time.time() + UserGrantedTreeBuildUtils(self.user).rebuild_user_granted_tree() + end = time.time() + logger.info( + 'Refresh user [{user}] org [{org}] perm tree, user {use_time:.2f}s' + ''.format(user=self.user, org=org, use_time=end-start) + ) + + def clean_user_perm_tree_nodes_for_legacy_org(self): + with tmp_to_root_org(): + """ Clean user legacy org node relations """ + user_relations = UserAssetGrantedTreeNodeRelation.objects.filter(user=self.user) + user_legacy_org_relations = user_relations.exclude(org_id__in=self.org_ids) + user_legacy_org_relations.delete() + + def get_user_need_refresh_orgs(self): + cached_org_ids = self.client.smembers(self.cache_key_user) + cached_org_ids = {oid.decode() for oid in cached_org_ids} + to_refresh_org_ids = set(self.org_ids) - cached_org_ids + to_refresh_orgs = Organization.objects.filter(id__in=to_refresh_org_ids) + logger.info(f'Need to refresh orgs: {to_refresh_orgs}') + return to_refresh_orgs + + def mark_user_orgs_refresh_finished(self, org_ids): + self.client.sadd(self.cache_key_user, *org_ids) + + +class UserPermTreeExpireUtil(_UserPermTreeCacheMixin): + """ 用户授权树过期工具 """ + + @lazyproperty + def cache_key_all_user(self): + return self.get_cache_key('*') + + def expire_perm_tree_for_nodes_assets(self, node_ids, asset_ids): + node_perm_ids = AssetPermissionUtil().get_permissions_for_nodes(node_ids, flat=True) + asset_perm_ids = AssetPermissionUtil().get_permissions_for_assets(asset_ids, flat=True) + perm_ids = set(node_perm_ids) | set(asset_perm_ids) + self.expire_perm_tree_for_perms(perm_ids) + + @tmp_to_root_org() + def expire_perm_tree_for_perms(self, perm_ids): + org_perm_ids = AssetPermission.objects.filter(id__in=perm_ids).values_list('org_id', 'id') + org_perms_mapper = defaultdict(set) + for org_id, perm_id in org_perm_ids: + org_perms_mapper[org_id].add(perm_id) + for org_id, perms_id in org_perms_mapper.items(): + org_ids = [org_id] + user_ids = AssetPermission.get_all_users_for_perms(perm_ids, flat=True) + self.expire_perm_tree_for_users_orgs(user_ids, org_ids) + + def expire_perm_tree_for_user_group(self, user_group): + group_ids = [user_group.id] + org_ids = [user_group.org_id] + self.expire_perm_tree_for_user_groups_orgs(group_ids, org_ids) + + def expire_perm_tree_for_user_groups_orgs(self, group_ids, org_ids): + user_ids = User.groups.through.objects.filter(usergroup_id__in=group_ids) \ + .values_list('user_id', flat=True).distinct() + self.expire_perm_tree_for_users_orgs(user_ids, org_ids) + + @on_transaction_commit + def expire_perm_tree_for_users_orgs(self, user_ids, org_ids): + org_ids = [str(oid) for oid in org_ids] + with self.client.pipline() as p: + for uid in user_ids: + cache_key = self.get_cache_key(uid) + p.srem(cache_key, *org_ids) + p.execute() + logger.info('Expire perm tree for users: [{}], orgs: [{}]'.format(user_ids, org_ids)) + + def expire_perm_tree_for_all_user(self): + keys = self.client.keys(self.cache_key_all_user) + with self.client.pipline() as p: + for k in keys: + p.delete(k) + p.execute() + logger.info('Expire all user perm tree') + diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index 9ba517125..81b4fc00e 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -1,9 +1,7 @@ -import time from collections import defaultdict from typing import List, Tuple from django.conf import settings -from django.core.cache import cache from django.db.models import Q, QuerySet from django.utils.translation import gettext as _ @@ -15,19 +13,15 @@ from assets.models import ( AssetQuerySet, NodeQuerySet ) -from orgs.models import Organization from orgs.utils import ( tmp_to_org, current_org, ensure_in_real_or_default_org, - tmp_to_root_org ) from common.db.models import output_as_string, UnionQuerySet -from common.decorator import on_transaction_commit from common.utils import get_logger from common.utils.common import lazyproperty, timeit -from perms.locks import UserGrantedTreeRebuildLock from perms.models import ( AssetPermission, PermNode, @@ -41,125 +35,6 @@ NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id') logger = get_logger(__name__) -class UserPermTreeCacheMixin: - """ 缓存数据 users: {org_id, org_id }, 记录用户授权树已经构建完成的组织集合 """ - - cache_key_template = 'perms.user.node_tree.built_orgs.user_id:{user_id}' - - def get_cache_key(self, user_id): - return self.cache_key_template.format(user_id=user_id) - - @lazyproperty - def client(self): - return cache.client.get_client(write=True) - - -class UserPermTreeRefreshUtil(UserPermTreeCacheMixin): - """ 用户授权树刷新工具, 针对某一个用户授权树的刷新 """ - - def __init__(self, user): - self.user = user - self.orgs = self.user.orgs.distinct() - self.org_ids = [str(o.id) for o in self.orgs] - - @lazyproperty - def cache_key_user(self): - return self.get_cache_key(self.user.id) - - @timeit - def refresh_if_need(self, force=False): - self.clean_user_perm_tree_nodes_for_legacy_org() - to_refresh_orgs = self.orgs if force else self.get_user_need_refresh_orgs() - if not to_refresh_orgs: - logger.info('Not have to refresh orgs') - return - with UserGrantedTreeRebuildLock(self.user.id): - for org in to_refresh_orgs: - self.rebuild_user_perm_tree_for_org(org) - self.mark_user_orgs_refresh_finished(to_refresh_orgs) - - def rebuild_user_perm_tree_for_org(self, org): - with tmp_to_org(org): - start = time.time() - UserGrantedTreeBuildUtils(self.user).rebuild_user_granted_tree() - end = time.time() - logger.info( - 'Refresh user [{user}] org [{org}] perm tree, user {use_time:.2f}s' - ''.format(user=self.user, org=org, use_time=end-start) - ) - - def clean_user_perm_tree_nodes_for_legacy_org(self): - with tmp_to_root_org(): - """ Clean user legacy org node relations """ - user_relations = UserAssetGrantedTreeNodeRelation.objects.filter(user=self.user) - user_legacy_org_relations = user_relations.exclude(org_id__in=self.org_ids) - user_legacy_org_relations.delete() - - def get_user_need_refresh_orgs(self): - cached_org_ids = self.client.smembers(self.cache_key_user) - cached_org_ids = {oid.decode() for oid in cached_org_ids} - to_refresh_org_ids = set(self.org_ids) - cached_org_ids - to_refresh_orgs = Organization.objects.filter(id__in=to_refresh_org_ids) - logger.info(f'Need to refresh orgs: {to_refresh_orgs}') - return to_refresh_orgs - - def mark_user_orgs_refresh_finished(self, org_ids): - self.client.sadd(self.cache_key_user, *org_ids) - - -class UserPermTreeExpireUtil(UserPermTreeCacheMixin): - """ 用户授权树过期工具 """ - - @lazyproperty - def cache_key_all_user(self): - return self.get_cache_key('*') - - def expire_perm_tree_for_nodes_assets(self, node_ids, asset_ids): - node_perm_ids = AssetPermissionUtil().get_permissions_for_nodes(node_ids, flat=True) - asset_perm_ids = AssetPermissionUtil().get_permissions_for_assets(asset_ids, flat=True) - perm_ids = set(node_perm_ids) | set(asset_perm_ids) - self.expire_perm_tree_for_perms(perm_ids) - - @tmp_to_root_org() - def expire_perm_tree_for_perms(self, perm_ids): - org_perm_ids = AssetPermission.objects.filter(id__in=perm_ids).values_list('org_id', 'id') - org_perms_mapper = defaultdict(set) - for org_id, perm_id in org_perm_ids: - org_perms_mapper[org_id].add(perm_id) - for org_id, perms_id in org_perms_mapper.items(): - org_ids = [org_id] - user_ids = AssetPermission.get_all_users_for_perms(perm_ids, flat=True) - self.expire_perm_tree_for_users_orgs(user_ids, org_ids) - - def expire_perm_tree_for_user_group(self, user_group): - group_ids = [user_group.id] - org_ids = [user_group.org_id] - self.expire_perm_tree_for_user_groups_orgs(group_ids, org_ids) - - def expire_perm_tree_for_user_groups_orgs(self, group_ids, org_ids): - user_ids = User.groups.through.objects.filter(usergroup_id__in=group_ids)\ - .values_list('user_id', flat=True).distinct() - self.expire_perm_tree_for_users_orgs(user_ids, org_ids) - - @on_transaction_commit - def expire_perm_tree_for_users_orgs(self, user_ids, org_ids): - org_ids = [str(oid) for oid in org_ids] - with self.client.pipline() as p: - for uid in user_ids: - cache_key = self.get_cache_key(uid) - p.srem(cache_key, *org_ids) - p.execute() - logger.info('Expire perm tree for users: [{}], orgs: [{}]'.format(user_ids, org_ids)) - - def expire_perm_tree_for_all_user(self): - keys = self.client.keys(self.cache_key_all_user) - with self.client.pipline() as p: - for k in keys: - p.delete(k) - p.execute() - logger.info('Expire all user perm tree') - - class UserGrantedUtilsBase: user: User diff --git a/apps/settings/signal_handlers.py b/apps/settings/signal_handlers.py index 58447ba79..7568de368 100644 --- a/apps/settings/signal_handlers.py +++ b/apps/settings/signal_handlers.py @@ -37,7 +37,7 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): if instance.is_name('PERM_SINGLE_ASSET_TO_UNGROUP_NODE'): """ 过期所有用户授权树 """ logger.debug('Expire all user perm tree') - from perms.utils.user_permission import UserPermTreeExpireUtil + from perms.utils import UserPermTreeExpireUtil UserPermTreeExpireUtil().expire_perm_tree_for_all_user() From eba43f6a13a7099c92541ac6b421479c19bcf5ad Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 12 Dec 2022 11:01:51 +0800 Subject: [PATCH 025/132] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20db-listen-p?= =?UTF-8?q?ort.db=5Finfo=20API=20=E8=BF=94=E5=9B=9E=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/db_listen_port.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/terminal/api/db_listen_port.py b/apps/terminal/api/db_listen_port.py index 4170a33d4..69dee6229 100644 --- a/apps/terminal/api/db_listen_port.py +++ b/apps/terminal/api/db_listen_port.py @@ -7,6 +7,7 @@ from rest_framework.viewsets import GenericViewSet from ..utils import db_port_manager, DBPortManager from applications import serializers +from assets.serializers.asset.database import DatabaseSerializer db_port_manager: DBPortManager @@ -32,5 +33,5 @@ class DBListenPortViewSet(GenericViewSet): def db_info(self, request, *args, **kwargs): port = request.query_params.get("port") db = db_port_manager.get_db_by_port(port) - serializer = serializers.AppSerializer(instance=db) + serializer = DatabaseSerializer(instance=db) return Response(data=serializer.data, status=status.HTTP_200_OK) From c861f390c7da203253d786aa6b1c1d1e9b63cfc9 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 12 Dec 2022 11:43:19 +0800 Subject: [PATCH 026/132] perf: k8s account ignore (#9188) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/utils/k8s.py | 2 +- .../api/user_permission/tree/node_with_asset.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py index 19134440f..3c5452272 100644 --- a/apps/assets/utils/k8s.py +++ b/apps/assets/utils/k8s.py @@ -118,7 +118,7 @@ class KubernetesTree: def as_account_tree_node(self, account, parent_info): username = account.username - name = f'{account.name}({account.username})' + name = str(account) pid = urlencode({'asset_id': self.tree_id}) i = self.create_tree_id(pid, 'account', username) parent_info.update({'account': username}) diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index fd07972ff..98b7cd261 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -8,7 +8,7 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.generics import get_object_or_404 -from assets.models import Asset +from assets.models import Asset, Account from assets.utils import KubernetesTree from assets.api import SerializeToTreeNodeMixin from perms.hands import Node @@ -141,12 +141,19 @@ class UserGrantedK8sAsTreeApi( asset = get_object_or_404(Asset, **kwargs) return asset + def get_accounts(self, asset): + util = PermAccountUtil() + accounts = util.get_permed_accounts_for_user(self.user, asset) + ignore_username = [Account.AliasAccount.INPUT, Account.AliasAccount.USER] + accounts = filter(lambda x: x.username not in ignore_username, accounts) + accounts = list(accounts) + return accounts + def list(self, request: Request, *args, **kwargs): tree_id = request.query_params.get('tree_id') key = request.query_params.get('key', {}) tree = [] - util = PermAccountUtil() parent_info = dict(parse_qsl(key)) account_username = parent_info.get('account') @@ -155,7 +162,7 @@ class UserGrantedK8sAsTreeApi( if tree_id and not account_username: asset = self.asset(asset_id) - accounts = util.get_permed_accounts_for_user(self.user, asset) + accounts = self.get_accounts(asset) asset_node = KubernetesTree(tree_id).as_asset_tree_node(asset) tree.append(asset_node) for account in accounts: From 408c6c568ad84fe56023ea008f23808e7f3ad0a0 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 12 Dec 2022 12:33:45 +0800 Subject: [PATCH 027/132] perf: account backup --- .../automations/backup_account/handlers.py | 41 ++++++------------- apps/assets/serializers/account/backup.py | 1 - 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/apps/assets/automations/backup_account/handlers.py b/apps/assets/automations/backup_account/handlers.py index 1575ce8fd..5c4ffce76 100644 --- a/apps/assets/automations/backup_account/handlers.py +++ b/apps/assets/automations/backup_account/handlers.py @@ -49,23 +49,6 @@ class BaseAccountHandler: header_fields[field] = str(v.label) return header_fields - @staticmethod - def load_auth(tp, value, system_user): - if value: - return value - if system_user: - return getattr(system_user, tp, '') - return '' - - @classmethod - def replace_auth(cls, account, system_user_dict): - system_user = system_user_dict.get(account.systemuser_id) - account.username = cls.load_auth('username', account.username, system_user) - account.password = cls.load_auth('password', account.password, system_user) - account.private_key = cls.load_auth('private_key', account.private_key, system_user) - account.public_key = cls.load_auth('public_key', account.public_key, system_user) - return account - @classmethod def create_row(cls, data, header_fields): data = cls.unpack_data(data) @@ -94,30 +77,30 @@ class AssetAccountHandler(BaseAccountHandler): return filename @classmethod - def create_data_map(cls, categories: list): + def create_data_map(cls, types: list): data_map = defaultdict(list) # TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作 qs = Account.objects.filter( - asset__platform__type__in=categories - ).annotate(category=F('asset__platform__type')) - print(qs, categories) + asset__platform__type__in=types + ).annotate(type=F('asset__platform__type')) + if not qs.exists(): return data_map - category_dict = {} + type_dict = {} for i in AllTypes.grouped_choices_to_objs(): for j in i['children']: - category_dict[j['value']] = j['display_name'] + type_dict[j['value']] = j['display_name'] header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first())) - account_category_map = defaultdict(list) + account_type_map = defaultdict(list) for account in qs: - account_category_map[account.category].append(account) + account_type_map[account.type].append(account) data_map = {} - for category, accounts in account_category_map.items(): - sheet_name = category_dict.get(category, category) + for tp, accounts in account_type_map.items(): + sheet_name = type_dict.get(tp, tp) data = AccountSecretSerializer(accounts, many=True).data data_map.update(cls.add_rows(data, header_fields, sheet_name)) @@ -140,9 +123,9 @@ class AccountBackupHandler: # Print task start date time_start = time.time() files = [] - categories = self.execution.categories + types = self.execution.types - data_map = AssetAccountHandler.create_data_map(categories) + data_map = AssetAccountHandler.create_data_map(types) if not data_map: return files diff --git a/apps/assets/serializers/account/backup.py b/apps/assets/serializers/account/backup.py index 06cf4e2f9..34121dadd 100644 --- a/apps/assets/serializers/account/backup.py +++ b/apps/assets/serializers/account/backup.py @@ -34,7 +34,6 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer): - trigger = LabeledChoiceField(choices=Trigger.choices, label=_('Trigger mode')) class Meta: model = AccountBackupPlanExecution From 2f3b1d3b666c6f465140fc6c68b6cc8ac2a4b96e Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 12 Dec 2022 17:12:04 +0800 Subject: [PATCH 028/132] fix: ticket xss inject --- apps/tickets/handlers/base.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/tickets/handlers/base.py b/apps/tickets/handlers/base.py index 7b311799f..40fb314e3 100644 --- a/apps/tickets/handlers/base.py +++ b/apps/tickets/handlers/base.py @@ -1,3 +1,5 @@ +from html import escape + from django.utils.translation import ugettext as _ from django.template.loader import render_to_string @@ -96,11 +98,19 @@ class BaseHandler: approve_info = _('{} {} the ticket').format(user_display, state_display) context = self._diff_prev_approve_context(state) context.update({'approve_info': approve_info}) + body = self.reject_html_script( + render_to_string('tickets/ticket_approve_diff.html', context) + ) data = { - 'body': render_to_string('tickets/ticket_approve_diff.html', context), + 'body': body, 'user': user, 'user_display': str(user), 'type': 'state', 'state': state } return self.ticket.comments.create(**data) + + @staticmethod + def reject_html_script(unsafe_html): + safe_html = escape(unsafe_html) + return safe_html From 1af86ccdfec2ab9263becf34eb7d96c5b39d1bf7 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 12 Dec 2022 17:30:38 +0800 Subject: [PATCH 029/132] perf: change secret_type to LabeledChoiceField --- apps/authentication/serializers/connect_token_secret.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index e4fd20be0..ac9345ae5 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -2,8 +2,10 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from acls.models import CommandGroup, CommandFilterACL +from assets.const import SecretType from assets.models import Asset, Account, Platform, Gateway, Domain from assets.serializers import PlatformSerializer, AssetProtocolsSerializer +from common.drf.fields import LabeledChoiceField from common.drf.fields import ObjectRelatedField from orgs.mixins.serializers import OrgResourceModelSerializerMixin from perms.serializers.permission import ActionChoicesField @@ -34,6 +36,7 @@ class _ConnectionTokenAssetSerializer(serializers.ModelSerializer): class _SimpleAccountSerializer(serializers.ModelSerializer): """ Account """ + secret_type = LabeledChoiceField(choices=SecretType.choices, required=False, label=_('Secret type')) class Meta: model = Account @@ -43,6 +46,7 @@ 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 From c121ac6b1d37c49842b4be4508f422c203c0cd42 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Tue, 13 Dec 2022 15:30:08 +0800 Subject: [PATCH 030/132] =?UTF-8?q?perf:=20OpenID=E6=94=AF=E6=8C=81PKCE?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/oidc/backends.py | 4 ++- apps/authentication/backends/oidc/views.py | 29 ++++++++++++++++++- apps/jumpserver/conf.py | 2 ++ apps/settings/serializers/auth/oidc.py | 5 ++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index 70e0f759c..f29bf95e5 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -88,7 +88,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend): """ @ssl_verification - def authenticate(self, request, nonce=None, **kwargs): + def authenticate(self, request, nonce=None, code_verifier=None, **kwargs): """ Authenticates users in case of the OpenID Connect Authorization code flow. """ log_prompt = "Process authenticate [OIDCAuthCodeBackend]: {}" logger.debug(log_prompt.format('start')) @@ -134,6 +134,8 @@ class OIDCAuthCodeBackend(OIDCBaseBackend): request, path=reverse(settings.AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME) ) } + if settings.AUTH_OPENID_PKCE and code_verifier: + token_payload['code_verifier'] = code_verifier if settings.AUTH_OPENID_CLIENT_AUTH_METHOD == 'client_secret_post': token_payload.update({ 'client_id': settings.AUTH_OPENID_CLIENT_ID, diff --git a/apps/authentication/backends/oidc/views.py b/apps/authentication/backends/oidc/views.py index 78019ac33..88088245d 100644 --- a/apps/authentication/backends/oidc/views.py +++ b/apps/authentication/backends/oidc/views.py @@ -9,7 +9,10 @@ """ +import base64 +import hashlib import time +import secrets from django.conf import settings from django.contrib import auth @@ -38,6 +41,19 @@ class OIDCAuthRequestView(View): http_method_names = ['get', ] + @staticmethod + def gen_code_verifier(length=128): + # length range 43 ~ 128 + return secrets.token_urlsafe(length - 32) + + @staticmethod + def gen_code_challenge(code_verifier, code_challenge_method): + if code_challenge_method == 'plain': + return code_verifier + h = hashlib.sha256(code_verifier.encode('ascii')).digest() + b = base64.urlsafe_b64encode(h) + return b.decode('ascii')[:-1] + def get(self, request): """ Processes GET requests. """ @@ -56,6 +72,16 @@ class OIDCAuthRequestView(View): ) }) + if settings.AUTH_OPENID_PKCE: + code_verifier = self.gen_code_verifier() + code_challenge_method = settings.AUTH_OPENID_CODE_CHALLENGE_METHOD or 'S256' + code_challenge = self.gen_code_challenge(code_verifier, code_challenge_method) + authentication_request_params.update({ + 'code_challenge_method': code_challenge_method, + 'code_challenge': code_challenge + }) + request.session['oidc_auth_code_verifier'] = code_verifier + # States should be used! They are recommended in order to maintain state between the # authentication request and the callback. if settings.AUTH_OPENID_USE_STATE: @@ -138,8 +164,9 @@ class OIDCAuthCallbackView(View): # Authenticates the end-user. next_url = request.session.get('oidc_auth_next_url', None) + code_verifier = request.session.get('oidc_auth_code_verifier', None) logger.debug(log_prompt.format('Process authenticate')) - user = auth.authenticate(nonce=nonce, request=request) + user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier) if user and user.is_valid: logger.debug(log_prompt.format('Login: {}'.format(user))) auth.login(self.request, user) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index c2bfba805..d43384987 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -270,6 +270,8 @@ class Config(dict): 'AUTH_OPENID_USER_ATTR_MAP': { 'name': 'name', 'username': 'preferred_username', 'email': 'email' }, + 'AUTH_OPENID_PKCE': False, + 'AUTH_OPENID_CODE_CHALLENGE_METHOD': 'S256', # OpenID 新配置参数 (version >= 1.5.9) 'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://oidc.example.com/', diff --git a/apps/settings/serializers/auth/oidc.py b/apps/settings/serializers/auth/oidc.py index ea25ca9d7..259cf9712 100644 --- a/apps/settings/serializers/auth/oidc.py +++ b/apps/settings/serializers/auth/oidc.py @@ -38,6 +38,11 @@ class CommonSettingSerializer(serializers.Serializer): help_text=_('User attr map present how to map OpenID user attr to ' 'jumpserver, username,name,email is jumpserver attr') ) + AUTH_OPENID_PKCE = serializers.BooleanField(required=False, label=_('Enable PKCE')) + AUTH_OPENID_CODE_CHALLENGE_METHOD = serializers.ChoiceField( + default='S256', label=_('Code challenge method'), + choices=(('S256', 'HS256'), ('plain', 'Plain')) + ) class KeycloakSettingSerializer(CommonSettingSerializer): From 6b33a54aef43d1dcbae9f65d22f9d4d4c0be0c9e Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Tue, 13 Dec 2022 15:31:47 +0800 Subject: [PATCH 031/132] =?UTF-8?q?perf:=20OpenID=E6=94=AF=E6=8C=81PKCE?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/auth.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index b4f1d3f9f..f1fec05cd 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -77,6 +77,8 @@ AUTH_OPENID_USE_NONCE = CONFIG.AUTH_OPENID_USE_NONCE AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER +AUTH_OPENID_PKCE = CONFIG.AUTH_OPENID_PKCE +AUTH_OPENID_CODE_CHALLENGE_METHOD = CONFIG.AUTH_OPENID_CODE_CHALLENGE_METHOD AUTH_OPENID_USER_ATTR_MAP = CONFIG.AUTH_OPENID_USER_ATTR_MAP AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login' AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback' From 1660362499dd0873609dbc9019f81dbd590e8918 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 13 Dec 2022 15:50:47 +0800 Subject: [PATCH 032/132] perf: validate ssh key --- apps/common/utils/encode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index 9eefde044..36cd4f224 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -190,8 +190,8 @@ def _parse_ssh_private_key(text, password=None): if is_openssh_format_key(text): return serialization.load_ssh_private_key(text, password=password) return serialization.load_pem_private_key(text, password=password) - except (ValueError, TypeError) as e: - raise e + except (ValueError, TypeError): + return None def is_openssh_format_key(text: bytes): From f42c0f667d4d9fd3a0880a6f1178e966e0786d89 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:36:00 +0800 Subject: [PATCH 033/132] perf: ticket applicant (#9202) Co-authored-by: feng <1304903146@qq.com> --- apps/tickets/api/ticket.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 912d61bc3..a402e23a1 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -60,6 +60,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): def perform_create(self, serializer): instance = serializer.save() + instance.applicant = self.request.user + instance.save(update_fields=['applicant']) instance.open() @action(detail=False, methods=[POST], permission_classes=[RBACPermission, ]) From 7b0d26bbff055b54884851594491e3c3d7a60c9a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 14 Dec 2022 15:24:36 +0800 Subject: [PATCH 034/132] perf: ticket applicant (#9205) Co-authored-by: feng <1304903146@qq.com> --- apps/tickets/serializers/ticket/ticket.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index c445af4c8..d21c44ec0 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -61,7 +61,6 @@ class TicketApplySerializer(TicketSerializer): org_id = serializers.CharField( required=True, max_length=36, allow_blank=True, label=_("Organization") ) - applicant = serializers.CharField(required=False, allow_blank=True) def get_applicant(self, applicant_id): current_user = self.context['request'].user From d60f9a7c69273ea41aafa6006f63d03e30ec2ca9 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 14 Dec 2022 17:19:35 +0800 Subject: [PATCH 035/132] =?UTF-8?q?perf:=20web=20selector=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/protocol.py | 13 +++++++------ apps/assets/serializers/asset/web.py | 7 +++---- apps/perms/utils/user_perm_tree.py | 14 ++++++-------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index d1ce663b8..7875e5ef4 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -1,4 +1,5 @@ from django.db import models + from common.db.models import ChoicesMixin __all__ = ['Protocol'] @@ -102,9 +103,9 @@ class Protocol(ChoicesMixin, models.TextChoices): 'port': 80, 'secret_types': ['password'], 'setting': { - 'username_selector': 'input[type=text]', - 'password_selector': 'input[type=password]', - 'submit_selector': 'button[type=submit]', + 'username_selector': 'name=username', + 'password_selector': 'name=password', + 'submit_selector': 'id=longin_button', } }, } @@ -112,7 +113,7 @@ class Protocol(ChoicesMixin, models.TextChoices): @classmethod def settings(cls): return { - **cls.device_protocols(), - **cls.database_protocols(), - **cls.cloud_protocols() + **cls.device_protocols(), + **cls.database_protocols(), + **cls.cloud_protocols() } diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py index 333795473..aa35e28e0 100644 --- a/apps/assets/serializers/asset/web.py +++ b/apps/assets/serializers/asset/web.py @@ -1,4 +1,3 @@ - from assets.models import Web from .common import AssetSerializer @@ -19,12 +18,12 @@ class WebSerializer(AssetSerializer): 'label': 'URL' }, 'username_selector': { - 'default': 'input[type=text]' + 'default': 'name=username' }, 'password_selector': { - 'default': 'input[type=password]' + 'default': 'name=password' }, 'submit_selector': { - 'default': 'button[type=submit]', + 'default': 'id=longin_button', }, } diff --git a/apps/perms/utils/user_perm_tree.py b/apps/perms/utils/user_perm_tree.py index d45b6a325..794ea3efc 100644 --- a/apps/perms/utils/user_perm_tree.py +++ b/apps/perms/utils/user_perm_tree.py @@ -3,24 +3,23 @@ from collections import defaultdict from django.core.cache import cache -from users.models import User +from common.decorator import on_transaction_commit +from common.utils import get_logger +from common.utils.common import lazyproperty, timeit from orgs.models import Organization from orgs.utils import ( tmp_to_org, tmp_to_root_org ) -from common.decorator import on_transaction_commit -from common.utils import get_logger -from common.utils.common import lazyproperty, timeit - from perms.locks import UserGrantedTreeRebuildLock from perms.models import ( AssetPermission, UserAssetGrantedTreeNodeRelation ) +from perms.utils.user_permission import UserGrantedTreeBuildUtils +from users.models import User from .permission import AssetPermissionUtil - logger = get_logger(__name__) __all__ = ['UserPermTreeRefreshUtil', 'UserPermTreeExpireUtil'] @@ -69,7 +68,7 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): end = time.time() logger.info( 'Refresh user [{user}] org [{org}] perm tree, user {use_time:.2f}s' - ''.format(user=self.user, org=org, use_time=end-start) + ''.format(user=self.user, org=org, use_time=end - start) ) def clean_user_perm_tree_nodes_for_legacy_org(self): @@ -142,4 +141,3 @@ class UserPermTreeExpireUtil(_UserPermTreeCacheMixin): p.delete(k) p.execute() logger.info('Expire all user perm tree') - From acfce4961c1b3c78f4a897ccf7bda7c2f9efb69a Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 14 Dec 2022 18:07:44 +0800 Subject: [PATCH 036/132] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=BB=84=E7=BB=87=E6=A0=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/tree/mixin.py | 4 +--- apps/perms/utils/user_perm_tree.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/perms/api/user_permission/tree/mixin.py b/apps/perms/api/user_permission/tree/mixin.py index d78a29b04..6c5eaac47 100644 --- a/apps/perms/api/user_permission/tree/mixin.py +++ b/apps/perms/api/user_permission/tree/mixin.py @@ -1,10 +1,8 @@ from rest_framework.request import Request -from users.models import User from common.http import is_true - from perms.utils import UserPermTreeRefreshUtil - +from users.models import User __all__ = ['RebuildTreeMixin'] diff --git a/apps/perms/utils/user_perm_tree.py b/apps/perms/utils/user_perm_tree.py index 794ea3efc..0ae9b0c61 100644 --- a/apps/perms/utils/user_perm_tree.py +++ b/apps/perms/utils/user_perm_tree.py @@ -59,7 +59,7 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): with UserGrantedTreeRebuildLock(self.user.id): for org in to_refresh_orgs: self.rebuild_user_perm_tree_for_org(org) - self.mark_user_orgs_refresh_finished(to_refresh_orgs) + self.mark_user_orgs_refresh_finished([str(org.id) for org in to_refresh_orgs]) def rebuild_user_perm_tree_for_org(self, org): with tmp_to_org(org): From cbf91e4c29cbe79537809fc36531d0382dbd33c0 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 15 Dec 2022 11:59:14 +0800 Subject: [PATCH 037/132] perf: ticket help text --- apps/jumpserver/api.py | 2 +- apps/tickets/serializers/ticket/apply_asset.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index b0b31f4c9..284ad429d 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -232,7 +232,7 @@ class DatesLoginMetricMixin: result = list(result) for i in result: tp = i['type'] - i['label'] = all_types_dict[tp] + i['label'] = all_types_dict.get(tp, tp) return result def get_dates_login_times_assets(self): diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index a0938cd99..8bf7b8563 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -13,10 +13,18 @@ __all__ = ['ApplyAssetSerializer', 'ApproveAssetSerializer'] asset_or_node_help_text = _("Select at least one asset or node") +apply_help_text = _('Support fuzzy search, and display up to 10 items') + class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer): - apply_assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Apply assets')) - apply_nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False, label=_('Apply nodes')) + apply_assets = ObjectRelatedField( + queryset=Asset.objects, many=True, required=False, + label=_('Apply assets'), help_text=apply_help_text + ) + apply_nodes = ObjectRelatedField( + queryset=Node.objects, many=True, required=False, + label=_('Apply nodes'), help_text=apply_help_text + ) apply_actions = ActionChoicesField(required=False, allow_null=True, label=_("Apply actions")) permission_model = AssetPermission From 840ca02223088bbe63fa81797c49bf760f4e663b Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 15 Dec 2022 15:21:51 +0800 Subject: [PATCH 038/132] perf: del redundant macos --- .../migrations/0114_remove_redundant_macos.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 apps/assets/migrations/0114_remove_redundant_macos.py diff --git a/apps/assets/migrations/0114_remove_redundant_macos.py b/apps/assets/migrations/0114_remove_redundant_macos.py new file mode 100644 index 000000000..1d8183e12 --- /dev/null +++ b/apps/assets/migrations/0114_remove_redundant_macos.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.14 on 2022-12-15 07:08 + +from django.db import migrations + + +def migrate_del_macos(apps, schema_editor): + db_alias = schema_editor.connection.alias + asset_model = apps.get_model('assets', 'Asset') + platform_model = apps.get_model('assets', 'Platform') + old_macos = platform_model.objects.using(db_alias).get( + name='MacOS', type='macos' + ) + new_macos = platform_model.objects.using(db_alias).get( + name='macOS', type='unix' + ) + asset_model.objects.using(db_alias).filter( + platform=old_macos).update(platform=new_macos) + + platform_model.objects.using(db_alias).filter(id=old_macos.id).delete() + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0113_auto_20221122_2015'), + ] + + operations = [ + migrations.RunPython(migrate_del_macos), + ] From cb7b31e8b80b41bdedc3046f30c2593249fc530d Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 15 Dec 2022 16:02:34 +0800 Subject: [PATCH 039/132] =?UTF-8?q?perf:=20perm=20account=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20alias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/__init__.py | 17 +- apps/assets/api/node.py | 185 ++------------------ apps/assets/api/tree.py | 173 ++++++++++++++++++ apps/assets/const/types.py | 66 +++++-- apps/assets/models/account.py | 7 +- apps/authentication/api/connection_token.py | 2 +- apps/perms/api/user_group_permission.py | 22 +-- apps/perms/api/user_permission/nodes.py | 8 +- apps/perms/serializers/user_permission.py | 13 +- apps/perms/utils/account.py | 2 +- 10 files changed, 278 insertions(+), 217 deletions(-) create mode 100644 apps/assets/api/tree.py diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 36f734030..8c71ed367 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -1,11 +1,12 @@ -from .mixin import * -from .category import * -from .platform import * -from .asset import * -from .label import * from .account import * -from .node import * -from .domain import * +from .asset import * from .automations import * -from .gathered_user import * +from .category import * +from .domain import * from .favorite_asset import * +from .gathered_user import * +from .label import * +from .mixin import * +from .node import * +from .platform import * +from .tree import * diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 85935dec2..b45b2170c 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -1,43 +1,37 @@ # ~*~ coding: utf-8 ~*~ -from functools import partial from collections import namedtuple, defaultdict -from django.core.exceptions import PermissionDenied +from functools import partial -from rest_framework import status -from rest_framework.generics import get_object_or_404 -from rest_framework.serializers import ValidationError -from rest_framework.response import Response -from rest_framework.decorators import action -from django.utils.translation import ugettext_lazy as _ from django.db.models.signals import m2m_changed +from django.utils.translation import ugettext_lazy as _ +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.generics import get_object_or_404 +from rest_framework.response import Response +from rest_framework.serializers import ValidationError -from common.const.http import POST -from common.exceptions import SomeoneIsDoingThis -from common.const.signals import PRE_REMOVE, POST_REMOVE -from common.mixins.api import SuggestionMixin from assets.models import Asset +from common.const.http import POST +from common.const.signals import PRE_REMOVE, POST_REMOVE +from common.exceptions import SomeoneIsDoingThis +from common.mixins.api import SuggestionMixin from common.utils import get_logger -from common.tree import TreeNodeSerializer -from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics +from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org +from .. import serializers from ..models import Node from ..tasks import ( update_node_assets_hardware_info_manual, test_node_assets_connectivity_manual, check_node_assets_amount_task ) -from .. import serializers -from ..const import AllTypes -from .mixin import SerializeToTreeNodeMixin -from assets.locks import NodeAddChildrenLock logger = get_logger(__file__) __all__ = [ - 'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi', - 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi', - 'NodeAddChildrenApi', 'NodeListAsTreeApi', 'NodeChildrenAsTreeApi', - 'NodeTaskCreateApi', 'CategoryTreeApi', + 'NodeViewSet', 'NodeAssetsApi', 'NodeAddAssetsApi', + 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi', + 'NodeAddChildrenApi', 'NodeTaskCreateApi', ] @@ -74,153 +68,6 @@ class NodeViewSet(SuggestionMixin, OrgBulkModelViewSet): return super().destroy(request, *args, **kwargs) -class NodeListAsTreeApi(generics.ListAPIView): - """ - 获取节点列表树 - [ - { - "id": "", - "name": "", - "pId": "", - "meta": "" - } - ] - """ - model = Node - serializer_class = TreeNodeSerializer - - @staticmethod - def to_tree_queryset(queryset): - queryset = [node.as_tree_node() for node in queryset] - return queryset - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.to_tree_queryset(queryset) - return queryset - - -class NodeChildrenApi(generics.ListCreateAPIView): - serializer_class = serializers.NodeSerializer - search_fields = ('value',) - - instance = None - is_initial = False - - def initial(self, request, *args, **kwargs): - self.instance = self.get_object() - return super().initial(request, *args, **kwargs) - - def perform_create(self, serializer): - with NodeAddChildrenLock(self.instance): - data = serializer.validated_data - _id = data.get("id") - value = data.get("value") - if not value: - value = self.instance.get_next_child_preset_name() - node = self.instance.create_child(value=value, _id=_id) - # 避免查询 full value - node._full_value = node.value - serializer.instance = node - - def get_object(self): - pk = self.kwargs.get('pk') or self.request.query_params.get('id') - key = self.request.query_params.get("key") - - if not pk and not key: - self.is_initial = True - if current_org.is_root(): - node = None - else: - node = Node.org_root() - return node - if pk: - node = get_object_or_404(Node, pk=pk) - else: - node = get_object_or_404(Node, key=key) - return node - - def get_org_root_queryset(self, query_all): - if query_all: - return Node.objects.all() - else: - return Node.org_root_nodes() - - def get_queryset(self): - query_all = self.request.query_params.get("all", "0") == "all" - - if self.is_initial and current_org.is_root(): - return self.get_org_root_queryset(query_all) - - if self.is_initial: - with_self = True - else: - with_self = False - - if not self.instance: - return Node.objects.none() - - if query_all: - queryset = self.instance.get_all_children(with_self=with_self) - else: - queryset = self.instance.get_children(with_self=with_self) - return queryset - - -class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): - """ - 节点子节点作为树返回, - [ - { - "id": "", - "name": "", - "pId": "", - "meta": "" - } - ] - - """ - model = Node - - def filter_queryset(self, queryset): - if not self.request.GET.get('search'): - return queryset - queryset = super().filter_queryset(queryset) - queryset = self.model.get_ancestor_queryset(queryset) - return queryset - - def list(self, request, *args, **kwargs): - nodes = self.filter_queryset(self.get_queryset()).order_by('value') - nodes = self.serialize_nodes(nodes, with_asset_amount=True) - assets = self.get_assets() - data = [*nodes, *assets] - return Response(data=data) - - def get_assets(self): - include_assets = self.request.query_params.get('assets', '0') == '1' - if not self.instance or not include_assets: - return [] - assets = self.instance.get_assets().only( - "id", "name", "address", "platform_id", - "org_id", "is_active", - ).prefetch_related('platform') - return self.serialize_assets(assets, self.instance.key) - - -class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView): - serializer_class = TreeNodeSerializer - - def check_permissions(self, request): - if not request.user.has_perm('assets.view_asset'): - raise PermissionDenied - return True - - def list(self, request, *args, **kwargs): - nodes = AllTypes.to_tree_nodes() - serializer = self.get_serializer(nodes, many=True) - return Response(data=serializer.data) - - class NodeAssetsApi(generics.ListAPIView): serializer_class = serializers.AssetSerializer diff --git a/apps/assets/api/tree.py b/apps/assets/api/tree.py new file mode 100644 index 000000000..a635fef0b --- /dev/null +++ b/apps/assets/api/tree.py @@ -0,0 +1,173 @@ +# ~*~ coding: utf-8 ~*~ + +from django.core.exceptions import PermissionDenied +from rest_framework.generics import get_object_or_404 +from rest_framework.response import Response + +from assets.locks import NodeAddChildrenLock +from common.tree import TreeNodeSerializer +from common.utils import get_logger +from orgs.mixins import generics +from orgs.utils import current_org +from .mixin import SerializeToTreeNodeMixin +from .. import serializers +from ..const import AllTypes +from ..models import Node + +logger = get_logger(__file__) +__all__ = [ + 'NodeChildrenApi', + 'NodeListAsTreeApi', + 'NodeChildrenAsTreeApi', + 'CategoryTreeApi', +] + + +class NodeListAsTreeApi(generics.ListAPIView): + """ + 获取节点列表树 + [ + { + "id": "", + "name": "", + "pId": "", + "meta": "" + } + ] + """ + model = Node + serializer_class = TreeNodeSerializer + + @staticmethod + def to_tree_queryset(queryset): + queryset = [node.as_tree_node() for node in queryset] + return queryset + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset = self.to_tree_queryset(queryset) + return queryset + + +class NodeChildrenApi(generics.ListCreateAPIView): + serializer_class = serializers.NodeSerializer + search_fields = ('value',) + + instance = None + is_initial = False + + def initial(self, request, *args, **kwargs): + self.instance = self.get_object() + return super().initial(request, *args, **kwargs) + + def perform_create(self, serializer): + with NodeAddChildrenLock(self.instance): + data = serializer.validated_data + _id = data.get("id") + value = data.get("value") + if not value: + value = self.instance.get_next_child_preset_name() + node = self.instance.create_child(value=value, _id=_id) + # 避免查询 full value + node._full_value = node.value + serializer.instance = node + + def get_object(self): + pk = self.kwargs.get('pk') or self.request.query_params.get('id') + key = self.request.query_params.get("key") + + if not pk and not key: + self.is_initial = True + if current_org.is_root(): + node = None + else: + node = Node.org_root() + return node + if pk: + node = get_object_or_404(Node, pk=pk) + else: + node = get_object_or_404(Node, key=key) + return node + + def get_org_root_queryset(self, query_all): + if query_all: + return Node.objects.all() + else: + return Node.org_root_nodes() + + def get_queryset(self): + query_all = self.request.query_params.get("all", "0") == "all" + + if self.is_initial and current_org.is_root(): + return self.get_org_root_queryset(query_all) + + if self.is_initial: + with_self = True + else: + with_self = False + + if not self.instance: + return Node.objects.none() + + if query_all: + queryset = self.instance.get_all_children(with_self=with_self) + else: + queryset = self.instance.get_children(with_self=with_self) + return queryset + + +class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): + """ + 节点子节点作为树返回, + [ + { + "id": "", + "name": "", + "pId": "", + "meta": "" + } + ] + + """ + model = Node + + def filter_queryset(self, queryset): + if not self.request.GET.get('search'): + return queryset + queryset = super().filter_queryset(queryset) + queryset = self.model.get_ancestor_queryset(queryset) + return queryset + + def list(self, request, *args, **kwargs): + nodes = self.filter_queryset(self.get_queryset()).order_by('value') + nodes = self.serialize_nodes(nodes, with_asset_amount=True) + assets = self.get_assets() + data = [*nodes, *assets] + return Response(data=data) + + def get_assets(self): + include_assets = self.request.query_params.get('assets', '0') == '1' + if not self.instance or not include_assets: + return [] + assets = self.instance.get_assets().only( + "id", "name", "address", "platform_id", + "org_id", "is_active", + ).prefetch_related('platform') + return self.serialize_assets(assets, self.instance.key) + + +class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView): + serializer_class = TreeNodeSerializer + + def check_permissions(self, request): + if not request.user.has_perm('assets.view_asset'): + raise PermissionDenied + return True + + def list(self, request, *args, **kwargs): + if request.query_params.get('key'): + nodes = [] + else: + nodes = AllTypes.to_tree_nodes() + serializer = self.get_serializer(nodes, many=True) + return Response(data=serializer.data) diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index f9cf83b85..c9012cd8c 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -1,14 +1,14 @@ +from collections import defaultdict from copy import deepcopy from common.db.models import ChoicesMixin from common.tree import TreeNode - from .category import Category -from .host import HostTypes -from .device import DeviceTypes -from .database import DatabaseTypes -from .web import WebTypes from .cloud import CloudTypes +from .database import DatabaseTypes +from .device import DeviceTypes +from .host import HostTypes +from .web import WebTypes class AllTypes(ChoicesMixin): @@ -54,7 +54,7 @@ class AllTypes(ChoicesMixin): item_name = item.replace('_enabled', '') methods = filter_platform_methods(category, tp, item_name) methods = [{'name': m['name'], 'id': m['id']} for m in methods] - automation_methods[item_name+'_methods'] = methods + automation_methods[item_name + '_methods'] = methods automation.update(automation_methods) constraints['automation'] = automation return constraints @@ -124,7 +124,7 @@ class AllTypes(ChoicesMixin): @staticmethod def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): node = TreeNode(**{ - 'id': choice.name, + 'id': pid + '_' + choice.name, 'name': choice.label, 'title': choice.label, 'pId': pid, @@ -135,16 +135,57 @@ class AllTypes(ChoicesMixin): node.meta = meta return node + @classmethod + def platform_to_node(cls, p, pid): + node = TreeNode(**{ + 'id': '{}'.format(p.id), + 'name': p.name, + 'title': p.name, + 'pId': pid, + 'isParent': True, + 'meta': { + 'type': 'platform' + } + }) + return node + @classmethod def to_tree_nodes(cls): - root = TreeNode(id='ROOT', name='类型节点', title='类型节点') + from ..models import Asset, Platform + asset_platforms = Asset.objects.all().values_list('platform_id', flat=True) + platform_count = defaultdict(int) + for platform_id in asset_platforms: + platform_count[platform_id] += 1 + + category_type_mapper = defaultdict(int) + platforms = Platform.objects.all() + tp_platforms = defaultdict(list) + + for p in platforms: + category_type_mapper[p.category + '_' + p.type] += platform_count[p.id] + category_type_mapper[p.category] += platform_count[p.id] + tp_platforms[p.category + '_' + p.type].append(p) + + root = TreeNode(id='ROOT', name='所有类型', title='所有类型', open=True, isParent=True) nodes = [root] for category, types in cls.category_types(): - category_node = cls.choice_to_node(category, 'ROOT', meta={'type': 'category'}) + meta = {'type': 'category', 'category': category.value} + category_node = cls.choice_to_node(category, 'ROOT', meta=meta) + category_count = category_type_mapper.get(category, 0) + category_node.name += f'({category_count})' nodes.append(category_node) + for tp in types: - tp_node = cls.choice_to_node(tp, category_node.id, meta={'type': 'type'}) + meta = {'type': 'type', 'category': category.value, '_type': tp.value} + tp_node = cls.choice_to_node(tp, category_node.id, opened=False, meta=meta) + tp_count = category_type_mapper.get(category + '_' + tp, 0) + tp_node.name += f'({tp_count})' nodes.append(tp_node) + + for p in tp_platforms.get(category + '_' + tp, []): + platform_node = cls.platform_to_node(p, tp_node.id) + platform_node.name += f'({platform_count.get(p.id, 0)})' + nodes.append(platform_node) return nodes @classmethod @@ -253,8 +294,3 @@ class AllTypes(ChoicesMixin): print("\t- Update platform: {}".format(platform.name)) platform_data = cls.get_type_default_platform(platform.category, platform.type) cls.create_or_update_by_platform_data(platform.name, platform_data) - - - - - diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index dbdcd21fd..b3742682d 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,7 +3,6 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from common.utils import lazyproperty - from .base import AbsConnectivity, BaseAccount __all__ = ['Account', 'AccountTemplate'] @@ -74,6 +73,12 @@ class Account(AbsConnectivity, BaseAccount): def platform(self): return self.asset.platform + @lazyproperty + def alias(self): + if self.username.startswith('@'): + return self.username + return self.name + def __str__(self): return '{}'.format(self.username) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 60528066e..c77a04fa7 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -266,7 +266,7 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView permed_account = util.validate_permission(user, asset, account_name) if not permed_account or not permed_account.actions: - msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( + msg = 'user `{}` not has asset `{}` permission for account `{}`'.format( user, asset, account_name ) raise PermissionDenied(msg) diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index c9abeee6c..850d1385b 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -6,14 +6,10 @@ from django.db.models import Q from rest_framework.generics import ListAPIView from rest_framework.response import Response -from common.utils import lazyproperty -from perms.models import AssetPermission -from assets.models import Asset, Node -from . import user_permission as uapi -from perms import serializers -from perms.utils import PermAccountUtil from assets.api.mixin import SerializeToTreeNodeMixin -from users.models import UserGroup +from assets.models import Asset, Node +from perms import serializers +from perms.models import AssetPermission __all__ = [ 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', @@ -101,11 +97,11 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView): granted_node_q |= Q(nodes__key=_key) granted_asset_q = ( - Q(granted_by_permissions__id__in=asset_perm_ids) & - ( - Q(nodes__key__startswith=f'{node.key}:') | - Q(nodes__key=node.key) - ) + Q(granted_by_permissions__id__in=asset_perm_ids) & + ( + Q(nodes__key__startswith=f'{node.key}:') | + Q(nodes__key=node.key) + ) ) assets = Asset.objects.filter( @@ -115,7 +111,7 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView): class UserGroupGrantedNodesApi(ListAPIView): - serializer_class = serializers.NodeGrantedSerializer + serializer_class = serializers.NodePermedSerializer rbac_perms = { 'list': 'perms.view_usergroupassets', } diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index 2fcfa5e2c..1972909e9 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- # import abc + from rest_framework.generics import ListAPIView from assets.models import Node +from common.utils import get_logger, lazyproperty from perms import serializers from perms.utils.user_permission import UserGrantedNodesQueryUtils -from common.utils import get_logger, lazyproperty - from .mixin import SelfOrPKUserMixin logger = get_logger(__name__) @@ -19,7 +19,7 @@ __all__ = [ class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView): - serializer_class = serializers.NodeGrantedSerializer + serializer_class = serializers.NodePermedSerializer def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -37,12 +37,14 @@ class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView): class UserAllPermedNodesApi(BaseUserPermedNodesApi): """ 用户授权的节点 """ + def get_nodes(self): return self.query_node_util.get_whole_tree_nodes() class UserPermedNodeChildrenApi(BaseUserPermedNodesApi): """ 用户授权的节点下的子节点 """ + def get_nodes(self): key = self.request.query_params.get('key') nodes = self.query_node_util.get_node_children(key) diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index ba0619edc..9b4ad26f0 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -11,7 +11,7 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField from perms.serializers.permission import ActionChoicesField __all__ = [ - 'NodeGrantedSerializer', 'AssetPermedSerializer', + 'NodePermedSerializer', 'AssetPermedSerializer', 'AccountsPermedSerializer' ] @@ -26,15 +26,14 @@ class AssetPermedSerializer(serializers.ModelSerializer): class Meta: model = Asset only_fields = [ - "id", "name", "address", - 'domain', 'platform', + "id", "name", "address", 'domain', 'platform', "comment", "org_id", "is_active", ] fields = only_fields + ['protocols', 'category', 'type', 'specific'] + ['org_name'] read_only_fields = fields -class NodeGrantedSerializer(serializers.ModelSerializer): +class NodePermedSerializer(serializers.ModelSerializer): class Meta: model = Node fields = [ @@ -48,6 +47,8 @@ class AccountsPermedSerializer(serializers.ModelSerializer): class Meta: model = Account - fields = ['id', 'name', 'has_username', 'username', - 'has_secret', 'secret_type', 'actions'] + fields = [ + 'alias', 'name', 'username', 'has_username', + 'has_secret', 'secret_type', 'actions' + ] read_only_fields = fields diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 7c0caf988..d4ce2e402 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -16,7 +16,7 @@ class PermAccountUtil(AssetPermissionUtil): :param account_name: 可能是 @USER @INPUT 字符串 """ permed_accounts = self.get_permed_accounts_for_user(user, asset) - accounts_mapper = {account.name: account for account in permed_accounts} + accounts_mapper = {account.alias: account for account in permed_accounts} account = accounts_mapper.get(account_name) return account From 69fe0b07fd8cd893e4cfa1a9debae58acfae85b8 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:08:19 +0800 Subject: [PATCH 040/132] perf: k8s tree (#9214) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/utils/k8s.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py index 3c5452272..7b287d740 100644 --- a/apps/assets/utils/k8s.py +++ b/apps/assets/utils/k8s.py @@ -112,7 +112,7 @@ class KubernetesTree: def as_asset_tree_node(self, asset): i = urlencode({'asset_id': self.tree_id}) node = self.create_tree_node( - i, str(asset.id), str(asset), 'asset', + i, str(asset.id), str(asset), 'asset', is_open=True, ) return node @@ -136,14 +136,14 @@ class KubernetesTree: return node @staticmethod - def create_tree_node(id_, pid, name, identity, icon='', is_container=False): + def create_tree_node(id_, pid, name, identity, icon='', is_container=False, is_open=False): node = { 'id': id_, 'name': name, 'title': name, 'pId': pid, 'isParent': not is_container, - 'open': False, + 'open': is_open, 'iconSkin': icon, 'meta': { 'type': 'k8s', From d7a793b4c45523357fa7578c818b0f186b03cb19 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 15 Dec 2022 18:47:54 +0800 Subject: [PATCH 041/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9k8s=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/utils/k8s.py | 1 - apps/perms/api/user_permission/tree/node_with_asset.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py index 7b287d740..2f66bf049 100644 --- a/apps/assets/utils/k8s.py +++ b/apps/assets/utils/k8s.py @@ -10,7 +10,6 @@ from kubernetes.client.exceptions import ApiException from rest_framework.generics import get_object_or_404 from common.utils import get_logger -from common.tree import TreeNode from assets.models import Account, Asset from ..const import CloudTypes, Category diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index 98b7cd261..e4d442b53 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -160,7 +160,7 @@ class UserGrantedK8sAsTreeApi( asset_id = parent_info.get('asset_id') asset_id = tree_id if not asset_id else asset_id - if tree_id and not account_username: + if tree_id and not key and not account_username: asset = self.asset(asset_id) accounts = self.get_accounts(asset) asset_node = KubernetesTree(tree_id).as_asset_tree_node(asset) @@ -170,6 +170,6 @@ class UserGrantedK8sAsTreeApi( account, parent_info, ) tree.append(account_node) - else: + elif key and account_username: tree = KubernetesTree(key).async_tree_node(parent_info) return Response(data=tree) From 11a58dc7ad3fe76f940aee28bb31ec8c3efc003a Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 16 Dec 2022 10:52:08 +0800 Subject: [PATCH 042/132] perf: k8s tree proxy --- apps/assets/utils/k8s.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py index 2f66bf049..8e703d4b6 100644 --- a/apps/assets/utils/k8s.py +++ b/apps/assets/utils/k8s.py @@ -18,13 +18,15 @@ logger = get_logger(__file__) class KubernetesClient: - def __init__(self, url, token): + def __init__(self, url, token, proxy=None): self.url = url self.token = token + self.proxy = proxy def get_api(self): configuration = client.Configuration() configuration.host = self.url + configuration.proxy = self.proxy configuration.verify_ssl = False configuration.api_key = {"authorization": "Bearer " + self.token} c = api_client.ApiClient(configuration=configuration) @@ -81,11 +83,23 @@ class KubernetesClient: data[namespace] = [pod_info, ] return data - @staticmethod - def get_kubernetes_data(app_id, username): + @classmethod + def get_proxy_url(cls, asset): + if not asset.domain: + return None + + gateway = asset.domain.select_gateway() + if not gateway: + return None + return f'{gateway.address}:{gateway.port}' + + @classmethod + def get_kubernetes_data(cls, app_id, username): asset = get_object_or_404(Asset, id=app_id) account = get_object_or_404(Account, asset=asset, username=username) - k8s = KubernetesClient(asset.address, account.secret) + k8s_url = f'{asset.address}:{asset.port}' + proxy_url = cls.get_proxy_url(asset) + k8s = cls(k8s_url, account.secret, proxy=proxy_url) return k8s.get_pods() From 9d80abadd87075b1d6b2d6b2726aecde4cf64d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Fri, 9 Dec 2022 16:35:29 +0800 Subject: [PATCH 043/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/services/services/base.py | 11 ++++++----- apps/common/management/commands/services/utils.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/common/management/commands/services/services/base.py b/apps/common/management/commands/services/services/base.py index 870014474..ddcb4feca 100644 --- a/apps/common/management/commands/services/services/base.py +++ b/apps/common/management/commands/services/services/base.py @@ -44,9 +44,12 @@ class BaseService(object): if self.is_running: msg = f'{self.name} is running: {self.pid}.' else: - msg = '\033[31m{} is stopped.\033[0m\nYou can manual start it to find the error: \n' \ - ' $ cd {}\n' \ - ' $ {}'.format(self.name, self.cwd, ' '.join(self.cmd)) + msg = f'{self.name} is stopped.' + if DEBUG: + msg = '\033[31m{} is stopped.\033[0m\nYou can manual start it to find the error: \n' \ + ' $ cd {}\n' \ + ' $ {}'.format(self.name, self.cwd, ' '.join(self.cmd)) + print(msg) # -- log -- @@ -147,7 +150,6 @@ class BaseService(object): self.remove_pid() break else: - time.sleep(1) continue def watch(self): @@ -203,4 +205,3 @@ class BaseService(object): logging.info(f'Remove old log: {to_delete_dir}') shutil.rmtree(to_delete_dir, ignore_errors=True) # -- end action -- - diff --git a/apps/common/management/commands/services/utils.py b/apps/common/management/commands/services/utils.py index afa642a1a..7ad6ea7f9 100644 --- a/apps/common/management/commands/services/utils.py +++ b/apps/common/management/commands/services/utils.py @@ -40,7 +40,8 @@ class ServicesUtil(object): service: BaseService service.start() self.files_preserve_map[service.name] = service.log_file - time.sleep(1) + + time.sleep(1) def stop(self): for service in self._services: From e969a0168994f80bcb0472944dc52cdbd1f9549c Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Thu, 15 Dec 2022 17:25:21 +0800 Subject: [PATCH 044/132] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/adhoc.py | 8 +++-- apps/ops/api/base.py | 17 ---------- apps/ops/api/job.py | 20 ++++++----- apps/ops/api/playbook.py | 11 ++++--- .../ops/migrations/0029_auto_20221215_1712.py | 33 +++++++++++++++++++ apps/ops/models/adhoc.py | 4 ++- apps/ops/models/job.py | 16 +++++---- apps/ops/models/playbook.py | 4 +-- apps/ops/serializers/adhoc.py | 3 +- apps/ops/serializers/job.py | 5 +-- apps/ops/serializers/playbook.py | 2 +- apps/ops/tasks.py | 3 +- 12 files changed, 79 insertions(+), 47 deletions(-) delete mode 100644 apps/ops/api/base.py create mode 100644 apps/ops/migrations/0029_auto_20221215_1712.py diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 158efc045..8caa59beb 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from .base import SelfBulkModelViewSet +from orgs.mixins.api import OrgBulkModelViewSet from ..models import AdHoc from ..serializers import ( AdHocSerializer @@ -10,7 +10,11 @@ __all__ = [ ] -class AdHocViewSet(SelfBulkModelViewSet): +class AdHocViewSet(OrgBulkModelViewSet): serializer_class = AdHocSerializer permission_classes = () model = AdHoc + + def get_queryset(self): + queryset = super().get_queryset() + return queryset.filter(creator=self.request.user) diff --git a/apps/ops/api/base.py b/apps/ops/api/base.py deleted file mode 100644 index c04e85e38..000000000 --- a/apps/ops/api/base.py +++ /dev/null @@ -1,17 +0,0 @@ -from rest_framework_bulk import BulkModelViewSet - -from common.mixins import CommonApiMixin - -__all__ = ['SelfBulkModelViewSet'] - - -class SelfBulkModelViewSet(CommonApiMixin, BulkModelViewSet): - - def get_queryset(self): - if hasattr(self, 'model'): - return self.model.objects.filter(creator=self.request.user) - else: - assert self.queryset is None, ( - "'%s' should not include a `queryset` attribute" - % self.__class__.__name__ - ) diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index bfaccbe39..d06331f4c 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -2,7 +2,6 @@ from rest_framework.views import APIView from django.shortcuts import get_object_or_404 from rest_framework.response import Response -from ops.api.base import SelfBulkModelViewSet from ops.models import Job, JobExecution from ops.serializers.job import JobSerializer, JobExecutionSerializer @@ -10,6 +9,7 @@ __all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'Jo from ops.tasks import run_ops_job_execution from ops.variables import JMS_JOB_VARIABLE_HELP +from orgs.mixins.api import OrgBulkModelViewSet def set_task_to_serializer_data(serializer, task): @@ -18,16 +18,17 @@ def set_task_to_serializer_data(serializer, task): setattr(serializer, "_data", data) -class JobViewSet(SelfBulkModelViewSet): +class JobViewSet(OrgBulkModelViewSet): serializer_class = JobSerializer permission_classes = () model = Job def get_queryset(self): - query_set = super().get_queryset() + queryset = super().get_queryset() + queryset = queryset.filter(creator=self.request.user) if self.action != 'retrieve': - return query_set.filter(instant=False) - return query_set + return queryset.filter(instant=False) + return queryset def perform_create(self, serializer): instance = serializer.save() @@ -48,7 +49,7 @@ class JobViewSet(SelfBulkModelViewSet): set_task_to_serializer_data(serializer, task) -class JobExecutionViewSet(SelfBulkModelViewSet): +class JobExecutionViewSet(OrgBulkModelViewSet): serializer_class = JobExecutionSerializer http_method_names = ('get', 'post', 'head', 'options',) permission_classes = () @@ -60,11 +61,12 @@ class JobExecutionViewSet(SelfBulkModelViewSet): set_task_to_serializer_data(serializer, task) def get_queryset(self): - query_set = super().get_queryset() + queryset = super().get_queryset() + queryset = queryset.filter(creator=self.request.user) job_id = self.request.query_params.get('job_id') if job_id: - query_set = query_set.filter(job_id=job_id) - return query_set + queryset = queryset.filter(job_id=job_id) + return queryset class JobRunVariableHelpAPIView(APIView): diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index 2d3d33e6b..009bfc2b8 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -2,11 +2,7 @@ import os import zipfile from django.conf import settings -from rest_framework_bulk import BulkModelViewSet - -from common.mixins import CommonApiMixin from orgs.mixins.api import OrgBulkModelViewSet -from .base import SelfBulkModelViewSet from ..exception import PlaybookNoValidEntry from ..models import Playbook from ..serializers.playbook import PlaybookSerializer @@ -20,11 +16,16 @@ def unzip_playbook(src, dist): fz.extract(file, dist) -class PlaybookViewSet(SelfBulkModelViewSet): +class PlaybookViewSet(OrgBulkModelViewSet): serializer_class = PlaybookSerializer permission_classes = () model = Playbook + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.filter(creator=self.request.user) + return queryset + def perform_create(self, serializer): instance = serializer.save() src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name) diff --git a/apps/ops/migrations/0029_auto_20221215_1712.py b/apps/ops/migrations/0029_auto_20221215_1712.py new file mode 100644 index 000000000..b7dc3ea6d --- /dev/null +++ b/apps/ops/migrations/0029_auto_20221215_1712.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.14 on 2022-12-15 09:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0028_auto_20221205_1627'), + ] + + operations = [ + migrations.AddField( + model_name='adhoc', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + migrations.AddField( + model_name='job', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + migrations.AddField( + model_name='jobexecution', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + migrations.AddField( + model_name='playbook', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + ] diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index d6fc27038..ebac9349f 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -9,10 +9,12 @@ from common.utils import get_logger __all__ = ["AdHoc"] +from orgs.mixins.models import JMSOrgBaseModel + logger = get_logger(__file__) -class AdHoc(JMSBaseModel): +class AdHoc(JMSOrgBaseModel): class Modules(models.TextChoices): shell = 'shell', _('Shell') winshell = 'win_shell', _('Powershell') diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 75592d66f..94c157cb8 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -11,13 +11,13 @@ from celery import current_task __all__ = ["Job", "JobExecution"] -from common.db.models import JMSBaseModel from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.mixin import PeriodTaskModelMixin from ops.variables import * +from orgs.mixins.models import JMSOrgBaseModel -class Job(JMSBaseModel, PeriodTaskModelMixin): +class Job(JMSOrgBaseModel, PeriodTaskModelMixin): class Types(models.TextChoices): adhoc = 'adhoc', _('Adhoc') playbook = 'playbook', _('Playbook') @@ -97,7 +97,7 @@ class Job(JMSBaseModel, PeriodTaskModelMixin): ordering = ['date_created'] -class JobExecution(JMSBaseModel): +class JobExecution(JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) task_id = models.UUIDField(null=True) status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') @@ -202,10 +202,11 @@ class JobExecution(JMSBaseModel): def gather_static_variables(self): default = { - JMS_USERNAME: self.creator.username, - JMS_JOB_ID: self.job.id, + JMS_JOB_ID: str(self.job.id), JMS_JOB_NAME: self.job.name, } + if self.creator: + default.update({JMS_USERNAME: self.creator.username}) return default @property @@ -255,7 +256,10 @@ class JobExecution(JMSBaseModel): this = self.__class__.objects.get(id=self.id) this.status = status_mapper.get(cb.status, cb.status) this.summary.update(cb.summary) - this.result.update(cb.result) + if this.result: + this.result.update(cb.result) + else: + this.result = cb.result this.finish_task() def finish_task(self): diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index 59688f76d..f92968762 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -5,11 +5,11 @@ from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ -from common.db.models import JMSBaseModel from ops.exception import PlaybookNoValidEntry +from orgs.mixins.models import JMSOrgBaseModel -class Playbook(JMSBaseModel): +class Playbook(JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name'), null=True) path = models.FileField(upload_to='playbooks/') diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 9883e104e..7db49fdcd 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -4,10 +4,11 @@ from __future__ import unicode_literals from rest_framework import serializers from common.drf.fields import ReadableHiddenField +from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import AdHoc -class AdHocSerializer(serializers.ModelSerializer): +class AdHocSerializer(BulkOrgResourceModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) row_count = serializers.IntegerField(read_only=True) size = serializers.IntegerField(read_only=True) diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index d5999a8df..0bc8a67f8 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -3,9 +3,10 @@ from rest_framework import serializers from common.drf.fields import ReadableHiddenField from ops.mixin import PeriodTaskSerializerMixin from ops.models import Job, JobExecution +from orgs.mixins.serializers import BulkOrgResourceModelSerializer -class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin): +class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) run_after_save = serializers.BooleanField(label=_("Run after save"), read_only=True, default=False, required=False) @@ -25,7 +26,7 @@ class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin): ] -class JobExecutionSerializer(serializers.ModelSerializer): +class JobExecutionSerializer(BulkOrgResourceModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) job_type = serializers.ReadOnlyField(label=_("Job type")) count = serializers.ReadOnlyField(label=_("Count")) diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py index 0334bdd45..bcfd75acd 100644 --- a/apps/ops/serializers/playbook.py +++ b/apps/ops/serializers/playbook.py @@ -12,7 +12,7 @@ def parse_playbook_name(path): return file_name.split(".")[-2] -class PlaybookSerializer(serializers.ModelSerializer): +class PlaybookSerializer(BulkOrgResourceModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) path = serializers.FileField(required=False) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index bc206cc27..19eb488e7 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -36,7 +36,8 @@ def run_ops_job(job_id): def run_ops_job_execution(execution_id, **kwargs): execution = get_object_or_none(JobExecution, id=execution_id) try: - execution.start() + with tmp_to_org(execution.org): + execution.start() except SoftTimeLimitExceeded: execution.set_error('Run timeout') logger.error("Run adhoc timeout") From bc47afb3290fe1a1d6704ed6af20396c4279f44e Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 16 Dec 2022 15:19:58 +0800 Subject: [PATCH 045/132] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20inventory?= =?UTF-8?q?=20=E5=90=8D=E5=AD=97=E7=94=9F=E6=88=90=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 5 +---- apps/ops/models/job.py | 17 ++++++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index c88c5c95f..6ac8cdb83 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -10,7 +10,7 @@ __all__ = ['JMSInventory'] class JMSInventory: def __init__(self, assets, account_policy='privileged_first', - account_prefer='root,Administrator', host_callback=None, unique_host_name=False): + account_prefer='root,Administrator', host_callback=None): """ :param assets: :param account_prefer: account username name if not set use account_policy @@ -21,7 +21,6 @@ class JMSInventory: self.account_policy = account_policy self.host_callback = host_callback self.exclude_hosts = {} - self.unique_host_name = unique_host_name @staticmethod def clean_assets(assets): @@ -114,8 +113,6 @@ class JMSInventory: 'secret': account.secret, 'secret_type': account.secret_type } if account else None } - if self.unique_host_name: - host['name'] += '({})'.format(asset.id) if host['jms_account'] and asset.platform.type == 'oracle': host['jms_account']['mode'] = 'sysdba' if account.privileged else None diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 94c157cb8..ce3c5c358 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -88,7 +88,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): @property def inventory(self): - return JMSInventory(self.assets.all(), self.runas_policy, self.runas, unique_host_name=True) + return JMSInventory(self.assets.all(), self.runas_policy, self.runas) def create_execution(self): return self.executions.create() @@ -133,26 +133,25 @@ class JobExecution(JMSOrgBaseModel): "status": "ok", "tasks": [], } - host_name = "{}({})".format(asset.name, asset.id) - if self.summary["excludes"].get(host_name, None): + if self.summary["excludes"].get(asset.name, None): asset_detail.update({"status": "excludes"}) result["detail"].append(asset_detail) break - if self.result["dark"].get(host_name, None): + if self.result["dark"].get(asset.name, None): asset_detail.update({"status": "failed"}) - for key, task in self.result["dark"][host_name].items(): + for key, task in self.result["dark"][asset.name].items(): task_detail = {"name": key, "output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))} asset_detail["tasks"].append(task_detail) - if self.result["failures"].get(host_name, None): + if self.result["failures"].get(asset.name, None): asset_detail.update({"status": "failed"}) - for key, task in self.result["failures"][host_name].items(): + for key, task in self.result["failures"][asset.name].items(): task_detail = {"name": key, "output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))} asset_detail["tasks"].append(task_detail) - if self.result["ok"].get(host_name, None): - for key, task in self.result["ok"][host_name].items(): + if self.result["ok"].get(asset.name, None): + for key, task in self.result["ok"][asset.name].items(): task_detail = {"name": key, "output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))} asset_detail["tasks"].append(task_detail) From 009669febe989ac73cbc0d62f63c8845fa2a5976 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 16 Dec 2022 15:52:02 +0800 Subject: [PATCH 046/132] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E5=AE=A1=E8=AE=A1api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 14 ++++++++++---- apps/audits/serializers.py | 13 +++++++++++++ apps/audits/urls/api_urls.py | 5 +---- apps/ops/api/job.py | 3 ++- apps/ops/models/job.py | 11 ++++++++++- apps/ops/serializers/job.py | 4 ++++ 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 397c96c6b..ba556d452 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -13,19 +13,25 @@ from common.drf.api import JMSReadOnlyModelViewSet from common.plugins.es import QuerySet as ESQuerySet from common.drf.filters import DatetimeRangeFilter from common.api import CommonGenericViewSet -from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin +from ops.models.job import JobAuditLog +from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet from orgs.utils import current_org -# from ops.models import CommandExecution -from . import filters from .backends import TYPE_ENGINE_MAPPING from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog -from .serializers import FTPLogSerializer, UserLoginLogSerializer +from .serializers import FTPLogSerializer, UserLoginLogSerializer, JobAuditLogSerializer from .serializers import ( OperateLogSerializer, OperateLogActionDetailSerializer, PasswordChangeLogSerializer ) +class JobAuditViewSet(OrgBulkModelViewSet): + serializer_class = JobAuditLogSerializer + http_method_names = ('get', 'head', 'options',) + permission_classes = () + model = JobAuditLog + + class FTPLogViewSet(CreateModelMixin, ListModelMixin, OrgGenericViewSet): model = FTPLog serializer_class = FTPLogSerializer diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 3b9772106..7ce6d7894 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -4,6 +4,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.drf.fields import LabeledChoiceField +from ops.models.job import JobAuditLog +from ops.serializers.job import JobExecutionSerializer from terminal.models import Session from . import models from .const import ( @@ -15,6 +17,17 @@ from .const import ( ) +class JobAuditLogSerializer(JobExecutionSerializer): + class Meta: + model = JobAuditLog + read_only_fields = ["timedelta", "time_cost", 'is_finished', 'date_start', + 'date_finished', + 'date_created', + 'is_success', + 'creator_name'] + fields = read_only_fields + [] + + class FTPLogSerializer(serializers.ModelSerializer): operate = LabeledChoiceField(choices=OperateChoices.choices, label=_("Operate")) diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py index 902c65fbf..fa8ee63fc 100644 --- a/apps/audits/urls/api_urls.py +++ b/apps/audits/urls/api_urls.py @@ -7,7 +7,6 @@ from rest_framework.routers import DefaultRouter from common import api as capi from .. import api - app_name = "audits" router = DefaultRouter() @@ -15,9 +14,7 @@ router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log') router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log') router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log') router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log') -# router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log') -# router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation') - +router.register(r'job-logs', api.JobAuditViewSet, 'job-log') urlpatterns = [ path('my-login-logs/', api.MyLoginLogAPIView.as_view(), name='my-login-log'), diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index d06331f4c..a49b1af90 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -3,9 +3,10 @@ from django.shortcuts import get_object_or_404 from rest_framework.response import Response from ops.models import Job, JobExecution +from ops.models.job import JobAuditLog from ops.serializers.job import JobSerializer, JobExecutionSerializer -__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobAssetDetail'] +__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobAssetDetail', ] from ops.tasks import run_ops_job_execution from ops.variables import JMS_JOB_VARIABLE_HELP diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index ce3c5c358..b63628b47 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _ from django.utils import timezone from celery import current_task -__all__ = ["Job", "JobExecution"] +__all__ = ["Job", "JobExecution", "JobAuditLog"] from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.mixin import PeriodTaskModelMixin @@ -286,3 +286,12 @@ class JobExecution(JMSOrgBaseModel): class Meta: ordering = ['-date_created'] + + +class JobAuditLog(JobExecution): + @property + def creator_name(self): + return self.creator.name + + class Meta: + proxy = True diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 0bc8a67f8..e56fca583 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -3,6 +3,7 @@ from rest_framework import serializers from common.drf.fields import ReadableHiddenField from ops.mixin import PeriodTaskSerializerMixin from ops.models import Job, JobExecution +from ops.models.job import JobAuditLog from orgs.mixins.serializers import BulkOrgResourceModelSerializer @@ -40,3 +41,6 @@ class JobExecutionSerializer(BulkOrgResourceModelSerializer): fields = read_only_fields + [ "job", "parameters" ] + + + From d040162d867b2cf500dcbecd9efe00c8f6cdd843 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 16 Dec 2022 15:53:59 +0800 Subject: [PATCH 047/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20session=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E6=B7=BB=E5=8A=A0=20Comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0114_remove_redundant_macos.py | 15 +++++++---- .../user_permission/tree/node_with_asset.py | 26 +++++++++---------- .../migrations/0062_auto_20221216_1529.py | 23 ++++++++++++++++ apps/terminal/models/session/session.py | 19 +++++++------- 4 files changed, 55 insertions(+), 28 deletions(-) create mode 100644 apps/terminal/migrations/0062_auto_20221216_1529.py diff --git a/apps/assets/migrations/0114_remove_redundant_macos.py b/apps/assets/migrations/0114_remove_redundant_macos.py index 1d8183e12..d24a3e74a 100644 --- a/apps/assets/migrations/0114_remove_redundant_macos.py +++ b/apps/assets/migrations/0114_remove_redundant_macos.py @@ -7,14 +7,19 @@ def migrate_del_macos(apps, schema_editor): db_alias = schema_editor.connection.alias asset_model = apps.get_model('assets', 'Asset') platform_model = apps.get_model('assets', 'Platform') - old_macos = platform_model.objects.using(db_alias).get( + old_macos = platform_model.objects.using(db_alias).filter( name='MacOS', type='macos' - ) - new_macos = platform_model.objects.using(db_alias).get( + ).first() + new_macos = platform_model.objects.using(db_alias).filter( name='macOS', type='unix' - ) + ).first() + + if not old_macos or not new_macos: + return + asset_model.objects.using(db_alias).filter( - platform=old_macos).update(platform=new_macos) + platform=old_macos + ).update(platform=new_macos) platform_model.objects.using(db_alias).filter(id=old_macos.id).delete() diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index 98b7cd261..66f195541 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -4,25 +4,24 @@ from urllib.parse import parse_qsl from django.conf import settings from django.db.models import F, Value, CharField from rest_framework.generics import ListAPIView +from rest_framework.generics import get_object_or_404 from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.generics import get_object_or_404 +from assets.api import SerializeToTreeNodeMixin from assets.models import Asset, Account from assets.utils import KubernetesTree -from assets.api import SerializeToTreeNodeMixin +from common.utils import get_object_or_none, lazyproperty +from common.utils.common import timeit from perms.hands import Node from perms.models import PermNode +from perms.utils import PermAccountUtil +from perms.utils.permission import AssetPermissionUtil from perms.utils.user_permission import ( UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, ) -from perms.utils import PermAccountUtil -from perms.utils.permission import AssetPermissionUtil -from common.utils import get_object_or_none, lazyproperty -from common.utils.common import timeit - -from ..mixin import SelfOrPKUserMixin from .mixin import RebuildTreeMixin +from ..mixin import SelfOrPKUserMixin __all__ = [ 'UserGrantedK8sAsTreeApi', @@ -31,8 +30,10 @@ __all__ = [ ] -class BaseUserNodeWithAssetAsTreeApi(SelfOrPKUserMixin, RebuildTreeMixin, SerializeToTreeNodeMixin, - ListAPIView): +class BaseUserNodeWithAssetAsTreeApi( + SelfOrPKUserMixin, RebuildTreeMixin, + SerializeToTreeNodeMixin, ListAPIView +): def list(self, request, *args, **kwargs): nodes, assets = self.get_nodes_assets() @@ -129,10 +130,7 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): return self.query_node_key -class UserGrantedK8sAsTreeApi( - SelfOrPKUserMixin, - ListAPIView -): +class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): """ 用户授权的K8s树 """ @staticmethod diff --git a/apps/terminal/migrations/0062_auto_20221216_1529.py b/apps/terminal/migrations/0062_auto_20221216_1529.py new file mode 100644 index 000000000..295cc8b55 --- /dev/null +++ b/apps/terminal/migrations/0062_auto_20221216_1529.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-12-16 07:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0061_rename_system_user_command_account'), + ] + + operations = [ + migrations.AddField( + model_name='session', + name='comment', + field=models.TextField(blank=True, null=True, verbose_name='Comment'), + ), + migrations.AddField( + model_name='session', + name='type', + field=models.CharField(db_index=True, default='normal', max_length=16), + ), + ] diff --git a/apps/terminal/models/session/session.py b/apps/terminal/models/session/session.py index 0a095a401..0241f10e5 100644 --- a/apps/terminal/models/session/session.py +++ b/apps/terminal/models/session/session.py @@ -3,24 +3,23 @@ from __future__ import unicode_literals import os import uuid -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.utils import timezone from django.conf import settings -from django.core.files.storage import default_storage from django.core.cache import cache +from django.core.files.storage import default_storage +from django.db import models +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ -from assets.models import Asset from assets.const import Protocol -from users.models import User -from orgs.mixins.models import OrgModelMixin -from django.db.models import TextChoices +from assets.models import Asset from common.utils import get_object_or_none, lazyproperty +from orgs.mixins.models import OrgModelMixin from terminal.backends import get_multi_command_storage +from users.models import User class Session(OrgModelMixin): - class LOGIN_FROM(TextChoices): + class LOGIN_FROM(models.TextChoices): ST = 'ST', 'SSH Terminal' RT = 'RT', 'RDP Terminal' WT = 'WT', 'Web Terminal' @@ -34,6 +33,7 @@ class Session(OrgModelMixin): account = models.CharField(max_length=128, verbose_name=_("Account"), db_index=True) protocol = models.CharField(default='ssh', max_length=16, db_index=True) login_from = models.CharField(max_length=2, choices=LOGIN_FROM.choices, default="ST", verbose_name=_("Login from")) + type = models.CharField(max_length=16, default='normal', db_index=True) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) is_success = models.BooleanField(default=True, db_index=True) is_finished = models.BooleanField(default=False, db_index=True) @@ -42,6 +42,7 @@ class Session(OrgModelMixin): terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.DO_NOTHING, db_constraint=False) date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) + comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) upload_to = 'replay' ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}' From 26794064b5d1c57675518c4a1bf2c8fabce20249 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 16 Dec 2022 17:16:14 +0800 Subject: [PATCH 048/132] =?UTF-8?q?perf:=20session=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/serializers/session.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py index b8ec7bd69..517262e0f 100644 --- a/apps/terminal/serializers/session.py +++ b/apps/terminal/serializers/session.py @@ -1,7 +1,9 @@ +from django.db import models from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from assets.const import Protocol +from common.drf.fields import LabeledChoiceField from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import Session @@ -11,17 +13,25 @@ __all__ = [ ] +class SessionType(models.TextChoices): + normal = 'normal', _('Normal') + tunnel = 'tunnel', _('Tunnel') + command = 'command', _('Command') + + class SessionSerializer(BulkOrgResourceModelSerializer): org_id = serializers.CharField(allow_blank=True) protocol = serializers.ChoiceField(choices=Protocol.choices, label=_("Protocol")) + type = LabeledChoiceField(choices=SessionType.choices, label=_("Type")) class Meta: model = Session fields_mini = ["id"] fields_small = fields_mini + [ - "user", "asset", "user_id", "asset_id", 'account', "protocol", - "login_from", "remote_addr", "is_success", - "is_finished", "has_replay", "date_start", "date_end", + "user", "asset", "user_id", "asset_id", 'account', + "protocol", 'type', "login_from", "remote_addr", + "is_success", "is_finished", "has_replay", "has_command", + "date_start", "date_end", "comment" ] fields_fk = ["terminal", ] fields_custom = ["can_replay", "can_join", "can_terminate"] From 675a41013ebc3bda8c4af16c8e033ffc858b4034 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 16 Dec 2022 18:37:27 +0800 Subject: [PATCH 049/132] perf: connect methods xpack --- apps/locale/zh/LC_MESSAGES/django.po | 2 +- apps/terminal/connect_methods.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 524d6d076..fa1799a13 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -5018,7 +5018,7 @@ msgstr "离线" #: terminal/const.py:81 terminal/const.py:82 terminal/const.py:83 #: terminal/const.py:84 terminal/const.py:85 msgid "DB Client" -msgstr "客户端" +msgstr "数据库客户端" #: terminal/exceptions.py:8 msgid "Bulk create not support" diff --git a/apps/terminal/connect_methods.py b/apps/terminal/connect_methods.py index db5dd4238..70e842e26 100644 --- a/apps/terminal/connect_methods.py +++ b/apps/terminal/connect_methods.py @@ -2,6 +2,7 @@ # from collections import defaultdict +from django.conf import settings from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ @@ -78,6 +79,10 @@ class NativeClient(TextChoices): return protocol return None + @classmethod + def xpack_methods(cls): + return [cls.sqlplus, cls.mstsc] + @classmethod def get_methods(cls, os='windows'): clients_map = cls.get_native_clients() @@ -87,6 +92,8 @@ class NativeClient(TextChoices): if isinstance(_clients, dict): _clients = _clients.get(os, _clients['default']) for client in _clients: + if not settings.XPACK_ENABLED and client in cls.xpack_methods(): + continue methods[protocol].append({ 'value': client.value, 'label': client.label, @@ -121,8 +128,10 @@ class AppletMethod: from .models import Applet, AppletHost methods = defaultdict(list) - has_applet_hosts = AppletHost.objects.all().exists() + if not settings.XPACK_ENABLED: + return methods + has_applet_hosts = AppletHost.objects.all().exists() applets = Applet.objects.filter(is_active=True) for applet in applets: for protocol in applet.protocols: From 69b16e4754c0f3d473607bc5d3d51e729276f136 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 19 Dec 2022 11:35:50 +0800 Subject: [PATCH 050/132] perf: asset type xpack (#9218) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/platform.py | 9 +++++++-- apps/assets/const/base.py | 23 +++++++++++++++++++++++ apps/assets/const/cloud.py | 4 ++++ apps/assets/const/database.py | 6 +++++- apps/assets/const/device.py | 4 ++++ apps/assets/const/host.py | 10 ++++++++-- apps/assets/const/types.py | 20 ++++++++++++++++---- apps/assets/const/web.py | 4 ++++ 8 files changed, 71 insertions(+), 9 deletions(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 411bf8b86..dbfcc2e4c 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -1,10 +1,10 @@ - +from jumpserver.utils import has_valid_xpack_license from common.drf.api import JMSModelViewSet from common.drf.serializers import GroupedChoiceSerializer from assets.models import Platform +from assets.const import AllTypes from assets.serializers import PlatformSerializer - __all__ = ['AssetPlatformViewSet'] @@ -22,6 +22,11 @@ class AssetPlatformViewSet(JMSModelViewSet): 'ops_methods': 'assets.view_platform' } + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.filter(type__in=AllTypes.get_types()) + return queryset + def get_object(self): pk = self.kwargs.get('pk', '') if pk.isnumeric(): diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index 35ad76757..aa5070d5e 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -1,5 +1,6 @@ from django.db.models import TextChoices +from jumpserver.utils import has_valid_xpack_license from .protocol import Protocol @@ -53,3 +54,25 @@ class BaseType(TextChoices): @classmethod def internal_platforms(cls): raise NotImplementedError + + @classmethod + def get_community_types(cls): + raise NotImplementedError + + @classmethod + def get_types(cls): + tps = [tp for tp in cls] + if not has_valid_xpack_license(): + tps = cls.get_community_types() + return tps + + @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 + ] + + diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index 7bc1864f1..1e16c9b13 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -49,3 +49,7 @@ class CloudTypes(BaseType): cls.PRIVATE: [{'name': 'Vmware-vSphere'}], cls.K8S: [{'name': 'Kubernetes'}], } + + @classmethod + def get_community_types(cls): + return [cls.K8S] diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py index 40b9f8aff..7df58e0a5 100644 --- a/apps/assets/const/database.py +++ b/apps/assets/const/database.py @@ -1,4 +1,3 @@ - from .base import BaseType @@ -62,3 +61,8 @@ class DatabaseTypes(BaseType): cls.REDIS: [{'name': 'Redis'}], } + @classmethod + def get_community_types(cls): + return [ + cls.MYSQL, cls.MARIADB, cls.MONGODB, cls.REDIS + ] diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index 1e2a5b717..45cfb05e7 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -52,3 +52,7 @@ class DeviceTypes(BaseType): cls.ROUTER: [], cls.FIREWALL: [] } + + @classmethod + def get_community_types(cls): + return [] diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 8be44db6f..ed0f9a631 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -34,7 +34,7 @@ class HostTypes(BaseType): def _get_protocol_constrains(cls) -> dict: return { '*': { - 'choices': ['ssh', 'telnet', 'vnc', 'rdp'] + 'choices': ['ssh', 'telnet', 'vnc', 'rdp'] }, cls.WINDOWS: { 'choices': ['rdp', 'ssh', 'vnc'] @@ -97,7 +97,7 @@ class HostTypes(BaseType): { 'name': 'RemoteAppHost', '_protocols': ['rdp', 'ssh'], - 'protocols_setting': { + 'protocols_setting': { 'ssh': { 'required': True } @@ -106,3 +106,9 @@ class HostTypes(BaseType): ], cls.OTHER_HOST: [] } + + @classmethod + def get_community_types(cls) -> list: + return [ + cls.LINUX, cls.UNIX, cls.WINDOWS, cls.OTHER_HOST + ] diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index c9012cd8c..6a0881e5d 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -62,14 +62,18 @@ class AllTypes(ChoicesMixin): @classmethod def types(cls, with_constraints=True): types = [] - for category, tps in cls.category_types(): + for category, type_cls in cls.category_types(): + tps = type_cls.get_types() types.extend([cls.serialize_type(category, tp, with_constraints) for tp in tps]) return types @classmethod def categories(cls, with_constraints=True): categories = [] - for category, tps in cls.category_types(): + for category, type_cls in cls.category_types(): + tps = type_cls.get_types() + if not tps: + continue category_data = { 'value': category.value, 'label': category.label, @@ -121,6 +125,13 @@ class AllTypes(ChoicesMixin): (Category.CLOUD, CloudTypes) ) + @classmethod + def get_types(cls): + tps = [] + for i in dict(cls.category_types()).values(): + tps.extend(i.get_types()) + return tps + @staticmethod def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): node = TreeNode(**{ @@ -168,14 +179,15 @@ class AllTypes(ChoicesMixin): root = TreeNode(id='ROOT', name='所有类型', title='所有类型', open=True, isParent=True) nodes = [root] - for category, types in cls.category_types(): + for category, type_cls in cls.category_types(): meta = {'type': 'category', 'category': category.value} category_node = cls.choice_to_node(category, 'ROOT', meta=meta) category_count = category_type_mapper.get(category, 0) category_node.name += f'({category_count})' nodes.append(category_node) - for tp in types: + tps = type_cls.get_types() + for tp in tps: meta = {'type': 'type', 'category': category.value, '_type': tp.value} tp_node = cls.choice_to_node(tp, category_node.id, opened=False, meta=meta) tp_count = category_type_mapper.get(category + '_' + tp, 0) diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py index 20c35b3a1..061030ab9 100644 --- a/apps/assets/const/web.py +++ b/apps/assets/const/web.py @@ -44,3 +44,7 @@ class WebTypes(BaseType): {'name': 'Website'}, ], } + + @classmethod + def get_community_types(cls): + return [] From ff16260024954ce86f66412c9a0314b13198a0fc Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 19 Dec 2022 15:35:28 +0800 Subject: [PATCH 051/132] perf: remove OrganizationMember model --- .../orgs/migrations/0013_alter_organization_options.py | 3 --- apps/orgs/migrations/0014_organization_builtin.py | 3 +++ apps/users/models/user.py | 2 +- utils/generate_fake_data/resources/users.py | 10 ---------- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/apps/orgs/migrations/0013_alter_organization_options.py b/apps/orgs/migrations/0013_alter_organization_options.py index 6dfd004da..e868a87a3 100644 --- a/apps/orgs/migrations/0013_alter_organization_options.py +++ b/apps/orgs/migrations/0013_alter_organization_options.py @@ -14,7 +14,4 @@ class Migration(migrations.Migration): name='organization', options={'permissions': (('view_rootorg', 'Can view root org'), ('view_alljoinedorg', 'Can view all joined org')), 'verbose_name': 'Organization'}, ), - migrations.DeleteModel( - name='OrganizationMember', - ), ] diff --git a/apps/orgs/migrations/0014_organization_builtin.py b/apps/orgs/migrations/0014_organization_builtin.py index 6541fe1a7..a9b407ffc 100644 --- a/apps/orgs/migrations/0014_organization_builtin.py +++ b/apps/orgs/migrations/0014_organization_builtin.py @@ -27,4 +27,7 @@ class Migration(migrations.Migration): field=models.BooleanField(default=False, verbose_name='Builtin'), ), migrations.RunPython(update_builtin_org), + migrations.DeleteModel( + name='OrganizationMember', + ), ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 2534837b5..d6137add9 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -871,7 +871,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): def delete(self, using=None, keep_parents=False): if self.pk == 1 or self.username == 'admin': return - return super(User, self).delete() + return super(User, self).delete(using=using, keep_parents=keep_parents) @classmethod def get_user_allowed_auth_backend_paths(cls, username): diff --git a/utils/generate_fake_data/resources/users.py b/utils/generate_fake_data/resources/users.py index 34a9d9703..c05e0793b 100644 --- a/utils/generate_fake_data/resources/users.py +++ b/utils/generate_fake_data/resources/users.py @@ -25,15 +25,6 @@ class UserGenerator(FakeDataGenerator): def pre_generate(self): self.group_ids = list(UserGroup.objects.all().values_list('id', flat=True)) - def set_org(self, users): - relations = [] - for u in users: - relations.append(OrganizationMember( - org_id=self.org.id, - user_id=u.id, - )) - OrganizationMember.objects.bulk_create(relations, ignore_conflicts=True) - def set_groups(self, users): relations = [] for i in users: @@ -55,5 +46,4 @@ class UserGenerator(FakeDataGenerator): ) users.append(u) users = User.objects.bulk_create(users, ignore_conflicts=True) - self.set_org(users) self.set_groups(users) From 92a198c00b20883dc5d911ed296357c2d7b632a1 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 19 Dec 2022 16:04:58 +0800 Subject: [PATCH 052/132] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=87=8D?= =?UTF-8?q?=E5=BB=BA=E7=94=A8=E6=88=B7=E6=8E=88=E6=9D=83=E6=A0=91=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=20(#9219)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化 用户授权树构建工具 * feat: 完成计算授权节点资产数量 * refactor: 重构重建用户授权树工具 * merge: v3 Co-authored-by: Bai --- apps/perms/models/asset_permission.py | 12 +- apps/perms/models/perm_node.py | 17 +- apps/perms/tasks.py | 2 +- apps/perms/utils/permission.py | 16 +- apps/perms/utils/user_perm_tree.py | 222 ++++++++++++++++++++++++-- apps/perms/utils/user_permission.py | 211 ------------------------ apps/users/models/user.py | 7 - 7 files changed, 228 insertions(+), 259 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 2277cafa6..e3b2afdca 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -15,6 +15,7 @@ from common.db.models import UnionQuerySet from common.utils import date_expired_default from perms.const import ActionChoices +from .perm_node import PermNode __all__ = ['AssetPermission', 'ActionChoices'] @@ -48,6 +49,10 @@ class AssetPermissionManager(OrgManager): def valid(self): return self.get_queryset().valid() + def get_expired_permissions(self): + now = local_now() + return self.get_queryset().filter(Q(date_start__lte=now) | Q(date_expired__gte=now)) + class AssetPermission(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) @@ -147,10 +152,3 @@ class AssetPermission(OrgModelMixin): if flat: return user_ids return User.objects.filter(id__in=user_ids) - - @classmethod - def get_expired_permissions(cls): - now = local_now() - return cls.objects.filter(Q(date_start__lte=now) | Q(date_expired__gte=now)) - - diff --git a/apps/perms/models/perm_node.py b/apps/perms/models/perm_node.py index ce061297e..4dddae98f 100644 --- a/apps/perms/models/perm_node.py +++ b/apps/perms/models/perm_node.py @@ -3,17 +3,20 @@ from django.utils.translation import ugettext_lazy as _ from django.db import models from django.db.models import F, TextChoices -from common.utils import lazyproperty -from common.db.models import BaseCreateUpdateModel from assets.models import Asset, Node, FamilyMixin, Account from orgs.mixins.models import OrgModelMixin +from common.utils import lazyproperty +from common.db.models import BaseCreateUpdateModel + + +class NodeFrom(TextChoices): + granted = 'granted', 'Direct node granted' + child = 'child', 'Have children node' + asset = 'asset', 'Direct asset granted' class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): - class NodeFrom(TextChoices): - granted = 'granted', 'Direct node granted' - child = 'child', 'Have children node' - asset = 'asset', 'Direct asset granted' + NodeFrom = NodeFrom user = models.ForeignKey('users.User', db_constraint=False, on_delete=models.CASCADE) node = models.ForeignKey('assets.Node', default=None, on_delete=models.CASCADE, @@ -46,6 +49,8 @@ class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpd class PermNode(Node): + NodeFrom = NodeFrom + class Meta: proxy = True ordering = [] diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 3df7a1728..57b9f48bd 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -29,7 +29,7 @@ logger = get_logger(__file__) @tmp_to_root_org() def check_asset_permission_expired(): """ 这里的任务要足够短,不要影响周期任务 """ - perms = AssetPermission.get_expired_permissions() + perms = AssetPermission.objects.get_expired_permissions() perm_ids = list(perms.distinct().values_list('id', flat=True)) logger.info(f'Checking expired permissions: {perm_ids}') UserPermTreeExpireUtil().expire_perm_tree_for_perms(perm_ids) diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 849bec02b..bbec44343 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -1,6 +1,5 @@ -import django -from django.db.models import QuerySet, Model -from collections.abc import Iterable +from django.db.models import QuerySet + from assets.models import Node, Asset from common.utils import get_logger @@ -90,11 +89,6 @@ class AssetPermissionUtil(object): perms = self.get_permissions(ids=perm_ids) return perms - @staticmethod - def get_permissions(ids): - perms = AssetPermission.objects.filter(id__in=ids).order_by('-date_expired') - return perms - @staticmethod def convert_to_queryset_if_need(objs_or_ids, model): if not objs_or_ids: @@ -107,5 +101,7 @@ class AssetPermissionUtil(object): ] return model.objects.filter(id__in=ids) - - + @staticmethod + def get_permissions(ids): + perms = AssetPermission.objects.filter(id__in=ids).order_by('-date_expired') + return perms diff --git a/apps/perms/utils/user_perm_tree.py b/apps/perms/utils/user_perm_tree.py index 0ae9b0c61..e40f20514 100644 --- a/apps/perms/utils/user_perm_tree.py +++ b/apps/perms/utils/user_perm_tree.py @@ -1,28 +1,38 @@ import time from collections import defaultdict +from django.conf import settings from django.core.cache import cache -from common.decorator import on_transaction_commit -from common.utils import get_logger -from common.utils.common import lazyproperty, timeit +from users.models import User +from assets.models import Asset +from assets.utils import NodeAssetsUtil from orgs.models import Organization from orgs.utils import ( + current_org, tmp_to_org, tmp_to_root_org ) +from common.decorator import on_transaction_commit +from common.utils import get_logger +from common.utils.common import lazyproperty, timeit +from common.db.models import output_as_string + from perms.locks import UserGrantedTreeRebuildLock from perms.models import ( AssetPermission, - UserAssetGrantedTreeNodeRelation + UserAssetGrantedTreeNodeRelation, + PermNode ) -from perms.utils.user_permission import UserGrantedTreeBuildUtils -from users.models import User + from .permission import AssetPermissionUtil logger = get_logger(__name__) -__all__ = ['UserPermTreeRefreshUtil', 'UserPermTreeExpireUtil'] +__all__ = [ + 'UserPermTreeRefreshUtil', + 'UserPermTreeExpireUtil' +] class _UserPermTreeCacheMixin: @@ -51,34 +61,34 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): @timeit def refresh_if_need(self, force=False): - self.clean_user_perm_tree_nodes_for_legacy_org() - to_refresh_orgs = self.orgs if force else self.get_user_need_refresh_orgs() + self._clean_user_perm_tree_for_legacy_org() + to_refresh_orgs = self.orgs if force else self._get_user_need_refresh_orgs() if not to_refresh_orgs: logger.info('Not have to refresh orgs') return with UserGrantedTreeRebuildLock(self.user.id): for org in to_refresh_orgs: - self.rebuild_user_perm_tree_for_org(org) - self.mark_user_orgs_refresh_finished([str(org.id) for org in to_refresh_orgs]) + self._rebuild_user_perm_tree_for_org(org) + self._mark_user_orgs_refresh_finished(to_refresh_orgs) - def rebuild_user_perm_tree_for_org(self, org): + def _rebuild_user_perm_tree_for_org(self, org): with tmp_to_org(org): start = time.time() - UserGrantedTreeBuildUtils(self.user).rebuild_user_granted_tree() + UserPermTreeBuildUtil(self.user).rebuild_user_perm_tree() end = time.time() logger.info( 'Refresh user [{user}] org [{org}] perm tree, user {use_time:.2f}s' - ''.format(user=self.user, org=org, use_time=end - start) + ''.format(user=self.user, org=org, use_time=end-start) ) - def clean_user_perm_tree_nodes_for_legacy_org(self): + def _clean_user_perm_tree_for_legacy_org(self): with tmp_to_root_org(): """ Clean user legacy org node relations """ user_relations = UserAssetGrantedTreeNodeRelation.objects.filter(user=self.user) user_legacy_org_relations = user_relations.exclude(org_id__in=self.org_ids) user_legacy_org_relations.delete() - def get_user_need_refresh_orgs(self): + def _get_user_need_refresh_orgs(self): cached_org_ids = self.client.smembers(self.cache_key_user) cached_org_ids = {oid.decode() for oid in cached_org_ids} to_refresh_org_ids = set(self.org_ids) - cached_org_ids @@ -86,7 +96,7 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): logger.info(f'Need to refresh orgs: {to_refresh_orgs}') return to_refresh_orgs - def mark_user_orgs_refresh_finished(self, org_ids): + def _mark_user_orgs_refresh_finished(self, org_ids): self.client.sadd(self.cache_key_user, *org_ids) @@ -141,3 +151,181 @@ class UserPermTreeExpireUtil(_UserPermTreeCacheMixin): p.delete(k) p.execute() logger.info('Expire all user perm tree') + + +class UserPermTreeBuildUtil(object): + node_only_fields = ('id', 'key', 'parent_key', 'org_id') + + def __init__(self, user): + self.user = user + self.user_perm_ids = AssetPermissionUtil().get_permissions_for_user(self.user, flat=True) + # {key: node} + self._perm_nodes_key_node_mapper = {} + + def rebuild_user_perm_tree(self): + self.clean_user_perm_tree() + if not self.user_perm_ids: + logger.info('User({}) not have permissions'.format(self.user)) + return + self.compute_perm_nodes() + self.compute_perm_nodes_asset_amount() + self.create_mapping_nodes() + + def clean_user_perm_tree(self): + UserAssetGrantedTreeNodeRelation.objects.filter(user=self.user).delete() + + def compute_perm_nodes(self): + self._compute_perm_nodes_for_direct() + self._compute_perm_nodes_for_direct_asset_if_need() + self._compute_perm_nodes_for_ancestor() + + def compute_perm_nodes_asset_amount(self): + """ 这里计算的是一个组织的授权树 """ + computed = self._only_compute_root_node_assets_amount_if_need() + if computed: + return + + nodekey_assetid_mapper = defaultdict(set) + org_id = current_org.id + for key in self.perm_node_keys_for_granted: + asset_ids = PermNode.get_all_asset_ids_by_node_key(org_id, key) + nodekey_assetid_mapper[key].update(asset_ids) + + for asset_id, node_id in self.direct_asset_id_node_id_pairs: + node_key = self.perm_nodes_id_key_mapper.get(node_id) + if not node_key: + continue + nodekey_assetid_mapper[node_key].add(asset_id) + + util = NodeAssetsUtil(self.perm_nodes, nodekey_assetid_mapper) + util.generate() + + for node in self.perm_nodes: + assets_amount = util.get_assets_amount(node.key) + node.assets_amount = assets_amount + + def create_mapping_nodes(self): + to_create = [] + for node in self.perm_nodes: + relation = UserAssetGrantedTreeNodeRelation( + user=self.user, + node=node, + node_key=node.key, + node_parent_key=node.parent_key, + node_from=node.node_from, + node_assets_amount=node.assets_amount, + org_id=node.org_id + ) + to_create.append(relation) + + UserAssetGrantedTreeNodeRelation.objects.bulk_create(to_create) + + def _compute_perm_nodes_for_direct(self): + """ 直接授权的节点(叶子节点)""" + for node in self.direct_nodes: + if self.has_any_ancestor_direct_permed(node): + continue + node.node_from = node.NodeFrom.granted + self._perm_nodes_key_node_mapper[node.key] = node + + def _compute_perm_nodes_for_direct_asset_if_need(self): + """ 直接授权的资产所在的节点(叶子节点)""" + if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + return + for node in self.direct_asset_nodes: + if self.has_any_ancestor_direct_permed(node): + continue + if node.key in self._perm_nodes_key_node_mapper: + continue + node.node_from = node.NodeFrom.asset + self._perm_nodes_key_node_mapper[node.key] = node + + def _compute_perm_nodes_for_ancestor(self): + """ 直接授权节点 和 直接授权资产所在节点 的所有祖先节点 (构造完整树) """ + ancestor_keys = set() + for node in self._perm_nodes_key_node_mapper.values(): + ancestor_keys.update(node.get_ancestor_keys()) + ancestor_keys -= set(self._perm_nodes_key_node_mapper.keys()) + + ancestors = PermNode.objects.filter(key__in=ancestor_keys).only(*self.node_only_fields) + for node in ancestors: + node.node_from = node.NodeFrom.child + self._perm_nodes_key_node_mapper[node.key] = node + + @lazyproperty + def perm_node_keys_for_granted(self): + keys = [ + key for key, node in self._perm_nodes_key_node_mapper.items() + if node.node_from == node.NodeFrom.granted + ] + return keys + + @lazyproperty + def perm_nodes_id_key_mapper(self): + mapper = { + node.id.hex: node.key + for key, node in self._perm_nodes_key_node_mapper.items() + } + return mapper + + def _only_compute_root_node_assets_amount_if_need(self): + if len(self.perm_nodes) != 1: + return False + root_node = self.perm_nodes[0] + if not root_node.is_org_root(): + return False + if root_node.node_from != root_node.NodeFrom.granted: + return False + root_node.granted_assets_amount = len(root_node.get_all_asset_ids()) + return True + + @lazyproperty + def perm_nodes(self): + """ 授权树的所有节点 """ + return list(self._perm_nodes_key_node_mapper.values()) + + def has_any_ancestor_direct_permed(self, node): + """ 任何一个祖先节点被直接授权 """ + return bool(set(node.get_ancestor_keys()) & set(self.direct_node_keys)) + + @lazyproperty + def direct_node_keys(self): + """ 直接授权的节点 keys """ + return {n.key for n in self.direct_nodes} + + @lazyproperty + def direct_nodes(self): + """ 直接授权的节点 """ + node_ids = AssetPermission.nodes.through.objects \ + .filter(assetpermission_id__in=self.user_perm_ids) \ + .values_list('node_id', flat=True).distinct() + nodes = PermNode.objects.filter(id__in=node_ids).only(*self.node_only_fields) + return nodes + + @lazyproperty + def direct_asset_nodes(self): + """ 获取直接授权的资产所在的节点 """ + node_ids = [node_id for asset_id, node_id in self.direct_asset_id_node_id_pairs] + nodes = PermNode.objects.filter(id__in=node_ids).distinct().only(*self.node_only_fields) + return nodes + + @lazyproperty + def direct_asset_id_node_id_pairs(self): + """ 直接授权的资产 id 和 节点 id """ + asset_node_pairs = Asset.nodes.through.objects \ + .filter(asset_id__in=self.direct_asset_ids) \ + .annotate( + str_asset_id=output_as_string('asset_id'), + str_node_id=output_as_string('node_id') + ).values_list('str_asset_id', 'str_node_id') + asset_node_pairs = list(asset_node_pairs) + return asset_node_pairs + + @lazyproperty + def direct_asset_ids(self): + """ 直接授权的资产 ids """ + asset_ids = AssetPermission.assets.through.objects \ + .filter(assetpermission_id__in=self.user_perm_ids) \ + .values_list('asset_id', flat=True) \ + .distinct() + return asset_ids diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index 81b4fc00e..674f562ff 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -51,217 +51,6 @@ class UserGrantedUtilsBase: return asset_perm_ids -class UserGrantedTreeBuildUtils(UserGrantedUtilsBase): - - def get_direct_granted_nodes(self) -> NodeQuerySet: - # 查询直接授权节点 - nodes = PermNode.objects.filter( - granted_by_permissions__id__in=self.asset_perm_ids - ).distinct() - return nodes - - @lazyproperty - def direct_granted_asset_ids(self) -> list: - # 3.15 - asset_ids = AssetPermission.assets.through.objects.filter( - assetpermission_id__in=self.asset_perm_ids - ).annotate( - asset_id_str=output_as_string('asset_id') - ).values_list( - 'asset_id_str', flat=True - ).distinct() - - asset_ids = list(asset_ids) - return asset_ids - - @ensure_in_real_or_default_org - def rebuild_user_granted_tree(self): - """ - 注意:调用该方法一定要被 `UserGrantedTreeRebuildLock` 锁住 - """ - user = self.user - - # 先删除旧的授权树🌲 - UserAssetGrantedTreeNodeRelation.objects.filter(user=user).delete() - - if not self.asset_perm_ids: - # 没有授权直接返回 - return - - nodes = self.compute_perm_nodes_tree() - self.compute_node_assets_amount(nodes) - if not nodes: - return - self.create_mapping_nodes(nodes) - - @timeit - def compute_perm_nodes_tree(self, node_only_fields=NODE_ONLY_FIELDS) -> list: - - # 查询直接授权节点 - nodes = self.get_direct_granted_nodes().only(*node_only_fields) - nodes = list(nodes) - - # 授权的节点 key 集合 - granted_key_set = {_node.key for _node in nodes} - - def _has_ancestor_granted(node: PermNode): - """ - 判断一个节点是否有授权过的祖先节点 - """ - ancestor_keys = set(node.get_ancestor_keys()) - return ancestor_keys & granted_key_set - - key2leaf_nodes_mapper = {} - - # 给授权节点设置 granted 标识,同时去重 - for node in nodes: - node: PermNode - if _has_ancestor_granted(node): - continue - node.node_from = NodeFrom.granted - key2leaf_nodes_mapper[node.key] = node - - # 查询授权资产关联的节点设置 - def process_direct_granted_assets(): - # 查询直接授权资产 - node_ids = {node_id_str for node_id_str, _ in self.direct_granted_asset_id_node_id_str_pairs} - # 查询授权资产关联的节点设置 2.80 - granted_asset_nodes = PermNode.objects.filter( - id__in=node_ids - ).distinct().only(*node_only_fields) - granted_asset_nodes = list(granted_asset_nodes) - - # 给资产授权关联的节点设置 is_asset_granted 标识,同时去重 - for node in granted_asset_nodes: - if _has_ancestor_granted(node): - continue - if node.key in key2leaf_nodes_mapper: - continue - node.node_from = NodeFrom.asset - key2leaf_nodes_mapper[node.key] = node - - if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - process_direct_granted_assets() - - leaf_nodes = key2leaf_nodes_mapper.values() - - # 计算所有祖先节点 - ancestor_keys = set() - for node in leaf_nodes: - ancestor_keys.update(node.get_ancestor_keys()) - - # 从祖先节点 key 中去掉同时也是叶子节点的 key - ancestor_keys -= key2leaf_nodes_mapper.keys() - # 查出祖先节点 - ancestors = PermNode.objects.filter(key__in=ancestor_keys).only(*node_only_fields) - ancestors = list(ancestors) - for node in ancestors: - node.node_from = NodeFrom.child - result = [*leaf_nodes, *ancestors] - return result - - @timeit - def create_mapping_nodes(self, nodes): - user = self.user - to_create = [] - - for node in nodes: - to_create.append(UserAssetGrantedTreeNodeRelation( - user=user, - node=node, - node_key=node.key, - node_parent_key=node.parent_key, - node_from=node.node_from, - node_assets_amount=node.assets_amount, - org_id=node.org_id - )) - - UserAssetGrantedTreeNodeRelation.objects.bulk_create(to_create) - - @timeit - def _fill_direct_granted_node_asset_ids_from_mem(self, nodes_key, mapper): - org_id = current_org.id - for key in nodes_key: - asset_ids = PermNode.get_all_asset_ids_by_node_key(org_id, key) - mapper[key].update(asset_ids) - - @lazyproperty - def direct_granted_asset_id_node_id_str_pairs(self): - node_asset_pairs = Asset.nodes.through.objects.filter( - asset_id__in=self.direct_granted_asset_ids - ).annotate( - asset_id_str=output_as_string('asset_id'), - node_id_str=output_as_string('node_id') - ).values_list( - 'node_id_str', 'asset_id_str' - ) - node_asset_pairs = list(node_asset_pairs) - return node_asset_pairs - - @timeit - def compute_node_assets_amount(self, nodes: List[PermNode]): - """ - 这里计算的是一个组织的 - """ - # 直接授权了根节点,直接计算 - if len(nodes) == 1: - node = nodes[0] - if node.node_from == NodeFrom.granted and node.key.isdigit(): - with tmp_to_org(node.org): - node.granted_assets_amount = len(node.get_all_asset_ids()) - return - - direct_granted_nodes_key = [] - node_id_key_mapper = {} - for node in nodes: - if node.node_from == NodeFrom.granted: - direct_granted_nodes_key.append(node.key) - node_id_key_mapper[node.id.hex] = node.key - - # 授权的节点和直接资产的映射 - nodekey_assetsid_mapper = defaultdict(set) - # 直接授权的节点,资产从完整树过来 - self._fill_direct_granted_node_asset_ids_from_mem( - direct_granted_nodes_key, nodekey_assetsid_mapper - ) - - # 处理直接授权资产 - # 直接授权资产,取节点与资产的关系 - node_asset_pairs = self.direct_granted_asset_id_node_id_str_pairs - node_asset_pairs = list(node_asset_pairs) - - for node_id, asset_id in node_asset_pairs: - if node_id not in node_id_key_mapper: - continue - node_key = node_id_key_mapper[node_id] - nodekey_assetsid_mapper[node_key].add(asset_id) - - util = NodeAssetsUtil(nodes, nodekey_assetsid_mapper) - util.generate() - - for node in nodes: - assets_amount = util.get_assets_amount(node.key) - node.assets_amount = assets_amount - - def get_whole_tree_nodes(self) -> list: - node_only_fields = NODE_ONLY_FIELDS + ('value', 'full_value') - nodes = self.compute_perm_nodes_tree(node_only_fields=node_only_fields) - self.compute_node_assets_amount(nodes) - - # 查询直接授权节点的子节点 - q = Q() - for node in self.get_direct_granted_nodes().only('key'): - q |= Q(key__startswith=f'{node.key}:') - - if q: - descendant_nodes = PermNode.objects.filter(q).distinct() - else: - descendant_nodes = PermNode.objects.none() - - nodes.extend(descendant_nodes) - return nodes - - class UserGrantedAssetsQueryUtils(UserGrantedUtilsBase): def get_favorite_assets(self) -> QuerySet: diff --git a/apps/users/models/user.py b/apps/users/models/user.py index d6137add9..b77d1151f 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -742,13 +742,6 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): def __str__(self): return '{0.name}({0.username})'.format(self) - @classmethod - def get_group_ids_by_user_id(cls, user_id): - group_ids = cls.groups.through.objects.filter(user_id=user_id) \ - .distinct().values_list('usergroup_id', flat=True) - group_ids = list(group_ids) - return group_ids - @property def receive_backends(self): return self.user_msg_subscription.receive_backends From e82eb8f3d1fe30c7f93398a3fd6df5e4526c07f9 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 19 Dec 2022 18:04:11 +0800 Subject: [PATCH 053/132] =?UTF-8?q?perf:=20=E6=89=B9=E9=87=8F=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=20(#9220)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/audits/api.py | 23 +++++++++------------ apps/jumpserver/api.py | 46 ++++++++++++++++++++++++++++++++++++++++++ apps/ops/const.py | 22 ++++++++++++++++++++ apps/ops/models/job.py | 24 ++++++---------------- 4 files changed, 83 insertions(+), 32 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index ba556d452..990d4353a 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -2,34 +2,29 @@ # from importlib import import_module -from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin -from django.db.models import F, Value -from django.db.models.functions import Concat from django.conf import settings -from rest_framework.permissions import IsAuthenticated from rest_framework import generics +from rest_framework.permissions import IsAuthenticated +from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin -from common.drf.api import JMSReadOnlyModelViewSet -from common.plugins.es import QuerySet as ESQuerySet -from common.drf.filters import DatetimeRangeFilter -from common.api import CommonGenericViewSet from ops.models.job import JobAuditLog -from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet +from common.api import CommonGenericViewSet +from common.drf.filters import DatetimeRangeFilter +from common.plugins.es import QuerySet as ESQuerySet from orgs.utils import current_org +from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet from .backends import TYPE_ENGINE_MAPPING from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog from .serializers import FTPLogSerializer, UserLoginLogSerializer, JobAuditLogSerializer from .serializers import ( - OperateLogSerializer, OperateLogActionDetailSerializer, - PasswordChangeLogSerializer + OperateLogSerializer, OperateLogActionDetailSerializer, PasswordChangeLogSerializer ) class JobAuditViewSet(OrgBulkModelViewSet): - serializer_class = JobAuditLogSerializer - http_method_names = ('get', 'head', 'options',) - permission_classes = () model = JobAuditLog + serializer_class = JobAuditLogSerializer + http_method_names = ('get', 'head', 'options') class FTPLogViewSet(CreateModelMixin, ListModelMixin, OrgGenericViewSet): diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 284ad429d..565ffbacb 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -16,6 +16,8 @@ from assets.const import AllTypes from terminal.models import Session, Command from terminal.utils import ComponentsPrometheusMetricsUtil from orgs.utils import current_org +from ops.const import JobStatus +from ops.models import Job, JobExecution from common.utils import lazyproperty from audits.models import UserLoginLog, PasswordChangeLog, OperateLog from audits.const import LoginStatusChoices @@ -118,12 +120,26 @@ class DateTimeMixin: queryset = Command.objects.filter(timestamp__gte=t) return queryset + @lazyproperty + def jobs_queryset(self): + t = self.days_to_datetime + queryset = Job.objects.filter(date_created__gte=t) + return queryset + + @lazyproperty + def jobs_executed_queryset(self): + t = self.days_to_datetime + queryset = JobExecution.objects.filter(date_created__gte=t) + return queryset + class DatesLoginMetricMixin: dates_list: list command_queryset: Command.objects sessions_queryset: Session.objects ftp_logs_queryset: OperateLog.objects + jobs_queryset: Job.objects + jobs_executed_queryset: JobExecution.objects login_logs_queryset: UserLoginLog.objects operate_logs_queryset: OperateLog.objects password_change_logs_queryset: PasswordChangeLog.objects @@ -299,6 +315,21 @@ class DatesLoginMetricMixin: def commands_danger_amount(self): return self.command_queryset.filter(risk_level=Command.RISK_LEVEL_DANGEROUS).count() + @lazyproperty + def jobs_amount(self): + return self.jobs_queryset.count() + + @lazyproperty + def jobs_unexecuted_amount(self): + executed_amount = self.jobs_executed_queryset.values( + 'job_id').order_by('job_id').distinct().count() + return self.jobs_amount - executed_amount + + @lazyproperty + def jobs_executed_failed_amount(self): + return self.jobs_executed_queryset.objects.filter( + status=JobStatus.failed).count() + @lazyproperty def sessions_amount(self): return self.sessions_queryset.count() @@ -408,6 +439,21 @@ class IndexApi(DateTimeMixin, DatesLoginMetricMixin, APIView): 'total_count_ftp_logs': self.ftp_logs_amount, }) + if _all or query_params.get('total_count') or query_params.get('total_count_jobs'): + data.update({ + 'total_count_jobs': self.jobs_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_jobs_unexecuted'): + data.update({ + 'total_count_jobs_unexecuted': self.jobs_unexecuted_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_jobs_executed_failed'): + data.update({ + 'total_count_jobs_executed_failed': self.jobs_executed_failed_amount, + }) + if _all or query_params.get('total_count') or query_params.get('total_count_type_to_assets_amount'): data.update({ 'total_count_type_to_assets_amount': self.get_type_to_assets, diff --git a/apps/ops/const.py b/apps/ops/const.py index 2f68efd3b..8288a663e 100644 --- a/apps/ops/const.py +++ b/apps/ops/const.py @@ -27,3 +27,25 @@ DEFAULT_PASSWORD_RULES = { 'length': DEFAULT_PASSWORD_LENGTH, 'symbol_set': string_punctuation } + + +class Types(models.TextChoices): + adhoc = 'adhoc', _('Adhoc') + playbook = 'playbook', _('Playbook') + + +class RunasPolicies(models.TextChoices): + privileged_only = 'privileged_only', _('Privileged Only') + privileged_first = 'privileged_first', _('Privileged First') + skip = 'skip', _('Skip') + + +class Modules(models.TextChoices): + shell = 'shell', _('Shell') + winshell = 'win_shell', _('Powershell') + + +class JobStatus(models.TextChoices): + running = 'running', _('Running') + success = 'success', _('Success') + failed = 'failed', _('Failed') diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index b63628b47..d9a4bdc58 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -14,23 +14,11 @@ __all__ = ["Job", "JobExecution", "JobAuditLog"] from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.mixin import PeriodTaskModelMixin from ops.variables import * +from ops.const import Types, Modules, RunasPolicies, JobStatus from orgs.mixins.models import JMSOrgBaseModel class Job(JMSOrgBaseModel, PeriodTaskModelMixin): - class Types(models.TextChoices): - adhoc = 'adhoc', _('Adhoc') - playbook = 'playbook', _('Playbook') - - class RunasPolicies(models.TextChoices): - privileged_only = 'privileged_only', _('Privileged Only') - privileged_first = 'privileged_first', _('Privileged First') - skip = 'skip', _('Skip') - - class Modules(models.TextChoices): - shell = 'shell', _('Shell') - winshell = 'win_shell', _('Powershell') - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, null=True, verbose_name=_('Name')) instant = models.BooleanField(default=False) @@ -100,7 +88,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): class JobExecution(JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) task_id = models.UUIDField(null=True) - status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') + status = models.CharField(max_length=16, verbose_name=_('Status'), default=JobStatus.running) job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='executions', null=True) parameters = models.JSONField(default=dict, verbose_name=_('Parameters')) result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) @@ -226,11 +214,11 @@ class JobExecution(JMSOrgBaseModel): @property def is_finished(self): - return self.status in ['success', 'failed'] + return self.status in [JobStatus.success, JobStatus.failed] @property def is_success(self): - return self.status == 'success' + return self.status == JobStatus.success @property def inventory_path(self): @@ -244,13 +232,13 @@ class JobExecution(JMSOrgBaseModel): def set_error(self, error): this = self.__class__.objects.get(id=self.id) # 重新获取一次,避免数据库超时连接超时 - this.status = 'failed' + this.status = JobStatus.failed this.summary.update({'error': str(error)}) this.finish_task() def set_result(self, cb): status_mapper = { - 'successful': 'success', + 'successful': JobStatus.success, } this = self.__class__.objects.get(id=self.id) this.status = status_mapper.get(cb.status, cb.status) From cdb89ee2f24be4ff03594bfce9fa3684ef387fd3 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 20 Dec 2022 10:35:05 +0800 Subject: [PATCH 054/132] perf: job executed fail api --- apps/jumpserver/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 565ffbacb..8c5f39642 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -327,7 +327,7 @@ class DatesLoginMetricMixin: @lazyproperty def jobs_executed_failed_amount(self): - return self.jobs_executed_queryset.objects.filter( + return self.jobs_executed_queryset.filter( status=JobStatus.failed).count() @lazyproperty From 24da1e7d9178a6de5e4f5baa8fea2139c32983cf Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Dec 2022 11:05:00 +0800 Subject: [PATCH 055/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token,=20=E8=8E=B7=E5=8F=96=20applet=20info?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 37 +++++---------------- apps/terminal/connect_methods.py | 6 ++-- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index c77a04fa7..782346fc5 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -20,7 +20,7 @@ from common.utils.django import get_request_os from orgs.mixins.api import RootOrgViewMixin from perms.models import ActionChoices from terminal.connect_methods import NativeClient, ConnectMethodUtil -from terminal.models import EndpointRule, Applet +from terminal.models import EndpointRule from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, @@ -34,30 +34,6 @@ class RDPFileClientProtocolURLMixin: request: Request get_serializer: callable - @staticmethod - def set_applet_info(token, rdp_options): - # remote-app - applet = Applet.objects.filter(name=token.connect_method).first() - if not applet: - return rdp_options - - cmdline = { - 'app_name': applet.name, - 'user_id': str(token.user.id), - 'asset_id': str(token.asset.id), - 'token_id': str(token.id) - } - - app = '||tinker' - rdp_options['remoteapplicationmode:i'] = '1' - rdp_options['alternate shell:s'] = app - rdp_options['remoteapplicationprogram:s'] = app - rdp_options['remoteapplicationname:s'] = app - - cmdline_b64 = base64.b64encode(json.dumps(cmdline).encode()).decode() - rdp_options['remoteapplicationcmdline:s'] = cmdline_b64 - return rdp_options - def get_rdp_file_info(self, token: ConnectionToken): rdp_options = { 'full address:s': '', @@ -114,9 +90,10 @@ class RDPFileClientProtocolURLMixin: rdp_options['session bpp:i'] = os.getenv('JUMPSERVER_COLOR_DEPTH', '32') rdp_options['audiomode:i'] = self.parse_env_bool('JUMPSERVER_DISABLE_AUDIO', 'false', '2', '0') - # 设置远程应用 - remote_app_options = token.get_remote_app_option() - rdp_options.update(remote_app_options) + # 设置远程应用, 不是 Mstsc + if token.connect_method != NativeClient.mstsc: + remote_app_options = token.get_remote_app_option() + rdp_options.update(remote_app_options) # 文件名 name = token.asset.name @@ -160,15 +137,17 @@ class RDPFileClientProtocolURLMixin: 'file': {} } - if connect_method_name == NativeClient.mstsc: + if connect_method_name == NativeClient.mstsc or connect_method_dict['type'] == 'applet': filename, content = self.get_rdp_file_info(token) data.update({ + 'protocol': 'rdp', 'file': { 'name': filename, 'content': content, } }) else: + print("Connect method: {}".format(connect_method_dict)) endpoint = self.get_smart_endpoint( protocol=connect_method_dict['endpoint_protocol'], asset=token.asset diff --git a/apps/terminal/connect_methods.py b/apps/terminal/connect_methods.py index 70e842e26..7b17dbe4e 100644 --- a/apps/terminal/connect_methods.py +++ b/apps/terminal/connect_methods.py @@ -220,11 +220,10 @@ class ConnectMethodUtil: for component, component_protocol in cls.protocols().items(): support = component_protocol['support'] + component_web_methods = component_protocol.get('web_methods', []) for protocol in support: # Web 方式 - protocol_web_methods = set(web_methods.get(protocol, [])) \ - & set(component_protocol.get('web_methods', [])) methods[protocol.value].extend([ { 'component': component.value, @@ -233,7 +232,8 @@ class ConnectMethodUtil: 'value': method.value, 'label': method.label, } - for method in protocol_web_methods + for method in web_methods.get(protocol, []) + if method in component_web_methods ]) # 客户端方式 From ba4d222eda47741120a898010e9121fec2d64408 Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 20 Dec 2022 11:25:44 +0800 Subject: [PATCH 056/132] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=20nodes-with-?= =?UTF-8?q?assets/tree/=20API=20=E5=BC=82=E5=B8=B8=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/user_permission.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index 674f562ff..8ee5992dd 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -54,12 +54,9 @@ class UserGrantedUtilsBase: class UserGrantedAssetsQueryUtils(UserGrantedUtilsBase): def get_favorite_assets(self) -> QuerySet: - favorite_asset_ids = FavoriteAsset.objects.filter( - user=self.user - ).values_list('asset_id', flat=True) - favorite_asset_ids = list(favorite_asset_ids) assets = self.get_all_granted_assets() - assets = assets.filter(id__in=favorite_asset_ids) + asset_ids = FavoriteAsset.objects.filter(user=self.user).values_list('asset_id', flat=True) + assets = assets.filter(id__in=list(asset_ids)) return assets def get_ungroup_assets(self) -> AssetQuerySet: @@ -83,8 +80,13 @@ class UserGrantedAssetsQueryUtils(UserGrantedUtilsBase): def get_all_granted_assets(self) -> QuerySet: nodes_assets = self.get_direct_granted_nodes_assets() assets = self.get_direct_granted_assets() - queryset = UnionQuerySet(nodes_assets, assets) - return queryset + # queryset = UnionQuerySet(nodes_assets, assets) + # return queryset + node_asset_ids = nodes_assets.values_list('id', flat=True) + direct_asset_ids = assets.values_list('id', flat=True) + asset_ids = list(node_asset_ids) + list(direct_asset_ids) + asset = Asset.objects.filter(id__in=asset_ids) + return asset def get_node_all_assets(self, id) -> Tuple[PermNode, QuerySet]: node = PermNode.objects.get(id=id) From c040564dc232fd97e048e1070c5fd56a755a63cc Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Dec 2022 13:47:12 +0800 Subject: [PATCH 057/132] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=20model=20=E7=9A=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/permissions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/rbac/permissions.py b/apps/rbac/permissions.py index 3b59a4812..1feade002 100644 --- a/apps/rbac/permissions.py +++ b/apps/rbac/permissions.py @@ -93,8 +93,10 @@ class RBACPermission(permissions.DjangoModelPermissions): try: queryset = self._queryset(view) model_cls = queryset.model + except AssertionError: + model_cls = None except Exception as e: - logger.error(e) + logger.error('Error get model class: {} of {}'.format(e, view)) model_cls = None return model_cls From dddff03336595ddf29a0f7a8445d50aa2511ef92 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 20 Dec 2022 14:53:25 +0800 Subject: [PATCH 058/132] perf: asset tree --- apps/perms/utils/user_perm_tree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/perms/utils/user_perm_tree.py b/apps/perms/utils/user_perm_tree.py index e40f20514..7937f2c6f 100644 --- a/apps/perms/utils/user_perm_tree.py +++ b/apps/perms/utils/user_perm_tree.py @@ -96,7 +96,8 @@ class UserPermTreeRefreshUtil(_UserPermTreeCacheMixin): logger.info(f'Need to refresh orgs: {to_refresh_orgs}') return to_refresh_orgs - def _mark_user_orgs_refresh_finished(self, org_ids): + def _mark_user_orgs_refresh_finished(self, orgs): + org_ids = [str(org.id) for org in orgs] self.client.sadd(self.cache_key_user, *org_ids) From 362cfb733cb17fc6dd0ea4500d1de2f527a76162 Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 20 Dec 2022 16:13:44 +0800 Subject: [PATCH 059/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20su-from-ac?= =?UTF-8?q?counts=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/account.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index aa22201b1..ebcfb1f09 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -6,7 +6,7 @@ from rest_framework.generics import CreateAPIView, ListAPIView from orgs.mixins.api import OrgBulkModelViewSet from common.mixins import RecordViewLogMixin -from assets.models import Account +from assets.models import Account, Asset from assets.filters import AccountFilterSet from assets.tasks import verify_accounts_connectivity from assets import serializers @@ -30,10 +30,18 @@ class AccountViewSet(OrgBulkModelViewSet): 'su_from_accounts': 'assets.view_account', } - @action(methods=['get'], detail=True, url_path='su-from-accounts') + @action(methods=['get'], detail=False, url_path='su-from-accounts') def su_from_accounts(self, request, *args, **kwargs): - account = get_object_or_404(Account, pk=self.kwargs['pk']) - accounts = account.get_su_from_accounts() + account_id = request.query_params.get('account') + asset_id = request.query_params.get('asset') + if account_id: + account = get_object_or_404(Account, pk=account_id) + accounts = account.get_su_from_accounts() + elif asset_id: + asset = get_object_or_404(Asset, pk=asset_id) + accounts = asset.accounts.all() + else: + accounts = [] serializer = serializers.AccountSerializer(accounts, many=True) return Response(data=serializer.data) From 54f720e99231bf714d95a456f68ee02f8863c171 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 16 Dec 2022 18:39:59 +0800 Subject: [PATCH 060/132] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 8 ++++---- apps/ops/models/job.py | 4 ++-- apps/ops/serializers/job.py | 7 ++----- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 1318974aa..724ba67ff 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:9f0b10566b4d35accd3a8766b14d6903243d93c5d7c55b208d930a189e590f2f -size 106125 +oid sha256:eaeedc4823f9b7e236b36a169b5587ef5988d3c3e529d6cbede6bae5e2b57ab8 +size 106349 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index fa1799a13..256794ea4 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -3067,19 +3067,19 @@ msgstr "跳过" #: ops/models/job.py:39 msgid "Chdir" -msgstr "" +msgstr "运行目录" #: ops/models/job.py:40 msgid "Timeout (Seconds)" -msgstr "" +msgstr "超市时间(秒)" #: ops/models/job.py:45 msgid "Runas" -msgstr "" +msgstr "运行用户" #: ops/models/job.py:47 msgid "Runas policy" -msgstr "" +msgstr "用户策略" #: ops/models/job.py:48 msgid "Use Parameter Define" diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index d9a4bdc58..d8a70d55e 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -104,7 +104,7 @@ class JobExecution(JMSOrgBaseModel): return { "ok": len(self.summary['ok']), "failed": len(self.summary['failures']) + len(self.summary['dark']), - "excludes": len(self.summary['excludes']), + "excludes": len(self.summary.get('excludes', {})), "total": self.job.assets.count() } @@ -121,7 +121,7 @@ class JobExecution(JMSOrgBaseModel): "status": "ok", "tasks": [], } - if self.summary["excludes"].get(asset.name, None): + if self.summary.get("excludes", None) and self.summary["excludes"].get(asset.name, None): asset_detail.update({"status": "excludes"}) result["detail"].append(asset_detail) break diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index e56fca583..81f77b915 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -37,10 +37,7 @@ class JobExecutionSerializer(BulkOrgResourceModelSerializer): read_only_fields = ["id", "task_id", "timedelta", "count", "time_cost", 'is_finished', 'date_start', 'date_finished', 'date_created', - 'is_success', 'task_id', 'short_id', 'job_type', 'creator'] + 'is_success', 'task_id', 'short_id', 'job_type'] fields = read_only_fields + [ - "job", "parameters" + "job", "parameters", "creator" ] - - - From d8cccfd40ff021ef96c602454905357a5030194e Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 20 Dec 2022 16:36:22 +0800 Subject: [PATCH 061/132] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96job=20executi?= =?UTF-8?q?on=20=E8=AF=A6=E6=83=85=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/job.py | 18 ++++++++---------- apps/ops/serializers/job.py | 6 +++--- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index d8a70d55e..4f003895e 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -99,14 +99,11 @@ class JobExecution(JMSOrgBaseModel): date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) @property - def count(self): - if self.is_finished and not self.summary.get('error', None): - return { - "ok": len(self.summary['ok']), - "failed": len(self.summary['failures']) + len(self.summary['dark']), - "excludes": len(self.summary.get('excludes', {})), - "total": self.job.assets.count() - } + def material(self): + if self.job.type == 'adhoc': + return "{}:{}".format(self.job.module, self.job.args) + if self.job.type == 'playbook': + return "{}:{}:{}".format(self.org.name, self.job.creator.name, self.job.playbook.name) @property def assent_result_detail(self): @@ -160,9 +157,10 @@ class JobExecution(JMSOrgBaseModel): def get_runner(self): inv = self.job.inventory inv.write_to_file(self.inventory_path) + self.summary = self.result = {"excludes": {}} if len(inv.exclude_hosts) > 0: - self.summary['excludes'] = inv.exclude_hosts - self.result['excludes'] = inv.exclude_hosts + self.summary.update({"excludes": inv.exclude_hosts}) + self.result.update({"excludes": inv.exclude_hosts}) self.save() if isinstance(self.parameters, str): diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 81f77b915..005b88cdc 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -30,14 +30,14 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): class JobExecutionSerializer(BulkOrgResourceModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) job_type = serializers.ReadOnlyField(label=_("Job type")) - count = serializers.ReadOnlyField(label=_("Count")) + material = serializers.ReadOnlyField(label=_("Material")) class Meta: model = JobExecution - read_only_fields = ["id", "task_id", "timedelta", "count", "time_cost", 'is_finished', 'date_start', + read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_finished', 'date_created', - 'is_success', 'task_id', 'short_id', 'job_type'] + 'is_success', 'task_id', 'short_id', 'job_type', 'summary', 'material'] fields = read_only_fields + [ "job", "parameters", "creator" ] From 754f8131b450874c84ac33226b0c2eb8c3ec9716 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Dec 2022 16:48:18 +0800 Subject: [PATCH 062/132] =?UTF-8?q?perf:=20=E5=86=85=E7=BD=AE=20applets=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=AE=89=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/base.py | 1 + apps/ops/migrations/0030_jobauditlog.py | 24 +++ apps/rbac/migrations/0001_initial.py | 35 +-- apps/rbac/models/role.py | 12 +- apps/terminal/api/applet/applet.py | 26 +-- apps/terminal/applets/__init__.py | 22 ++ apps/terminal/applets/chrome/README.md | 7 + apps/terminal/applets/chrome/app.py | 201 ++++++++++++++++++ apps/terminal/applets/chrome/common.py | 197 +++++++++++++++++ apps/terminal/applets/chrome/i18n.yml | 4 + apps/terminal/applets/chrome/icon.png | Bin 0 -> 3043 bytes apps/terminal/applets/chrome/main.py | 22 ++ apps/terminal/applets/chrome/manifest.yml | 12 ++ apps/terminal/applets/chrome/setup.yml | 6 + .../applets/chrome/test_data_example.json | 41 ++++ apps/terminal/management/__init__.py | 0 apps/terminal/management/commands/__init__.py | 0 .../commands/install_builtin_applets.py | 9 + .../migrations/0063_applet_builtin.py | 18 ++ apps/terminal/models/applet/applet.py | 35 ++- jms | 19 +- 21 files changed, 649 insertions(+), 42 deletions(-) create mode 100644 apps/ops/migrations/0030_jobauditlog.py create mode 100644 apps/terminal/applets/__init__.py create mode 100644 apps/terminal/applets/chrome/README.md create mode 100644 apps/terminal/applets/chrome/app.py create mode 100644 apps/terminal/applets/chrome/common.py create mode 100644 apps/terminal/applets/chrome/i18n.yml create mode 100644 apps/terminal/applets/chrome/icon.png create mode 100644 apps/terminal/applets/chrome/main.py create mode 100644 apps/terminal/applets/chrome/manifest.yml create mode 100644 apps/terminal/applets/chrome/setup.yml create mode 100644 apps/terminal/applets/chrome/test_data_example.json create mode 100644 apps/terminal/management/__init__.py create mode 100644 apps/terminal/management/commands/__init__.py create mode 100644 apps/terminal/management/commands/install_builtin_applets.py create mode 100644 apps/terminal/migrations/0063_applet_builtin.py diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 3b19bb16e..0ce5e9da3 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -35,6 +35,7 @@ def parse_sentinels_host(sentinels_host): VERSION = const.VERSION BASE_DIR = const.BASE_DIR PROJECT_DIR = const.PROJECT_DIR +APP_DIR = os.path.join(PROJECT_DIR, 'apps') DATA_DIR = os.path.join(PROJECT_DIR, 'data') ANSIBLE_DIR = os.path.join(DATA_DIR, 'ansible') CERTS_DIR = os.path.join(DATA_DIR, 'certs') diff --git a/apps/ops/migrations/0030_jobauditlog.py b/apps/ops/migrations/0030_jobauditlog.py new file mode 100644 index 000000000..ff933e447 --- /dev/null +++ b/apps/ops/migrations/0030_jobauditlog.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-12-20 07:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0029_auto_20221215_1712'), + ] + + operations = [ + migrations.CreateModel( + name='JobAuditLog', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('ops.jobexecution',), + ), + ] diff --git a/apps/rbac/migrations/0001_initial.py b/apps/rbac/migrations/0001_initial.py index 8be92f6d3..624b88dd3 100644 --- a/apps/rbac/migrations/0001_initial.py +++ b/apps/rbac/migrations/0001_initial.py @@ -1,16 +1,17 @@ # Generated by Django 3.1.13 on 2021-11-19 08:29 -import common.db.models -from django.conf import settings +import uuid + import django.contrib.auth.models import django.contrib.contenttypes.models -from django.db import migrations, models import django.db.models.deletion -import uuid +from django.conf import settings +from django.db import migrations, models + +import common.db.models class Migration(migrations.Migration): - initial = True dependencies = [ @@ -28,7 +29,8 @@ class Migration(migrations.Migration): ], options={ 'verbose_name': 'Menu permission', - 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workbench view')], + 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), + ('view_workspace', 'Can view workbench view')], 'default_permissions': [], }, ), @@ -41,8 +43,9 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), - ('scope', models.CharField(choices=[('system', 'System'), ('org', 'Organization')], default='system', max_length=128, verbose_name='Scope')), - ('builtin', models.BooleanField(default=False, verbose_name='Built-in')), + ('scope', models.CharField(choices=[('system', 'System'), ('org', 'Organization')], default='system', + max_length=128, verbose_name='Scope')), + ('builtin', models.BooleanField(default=False, verbose_name='Builtin')), ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), ], ), @@ -82,10 +85,15 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('scope', models.CharField(choices=[('system', 'System'), ('org', 'Organization')], default='system', max_length=128, verbose_name='Scope')), - ('org', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to='orgs.organization', verbose_name='Organization')), - ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to='rbac.role', verbose_name='Role')), - ('user', models.ForeignKey(on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='role_bindings', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('scope', models.CharField(choices=[('system', 'System'), ('org', 'Organization')], default='system', + max_length=128, verbose_name='Scope')), + ('org', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='role_bindings', to='orgs.organization', + verbose_name='Organization')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', + to='rbac.role', verbose_name='Role')), + ('user', models.ForeignKey(on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='role_bindings', + to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ 'verbose_name': 'Role binding', @@ -95,7 +103,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name='role', name='permissions', - field=models.ManyToManyField(blank=True, related_name='roles', to='rbac.Permission', verbose_name='Permissions'), + field=models.ManyToManyField(blank=True, related_name='roles', to='rbac.Permission', + verbose_name='Permissions'), ), migrations.AlterUniqueTogether( name='role', diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py index 05783f598..1eff7c15c 100644 --- a/apps/rbac/models/role.py +++ b/apps/rbac/models/role.py @@ -1,11 +1,11 @@ -from django.utils.translation import ugettext_lazy as _, gettext from django.db import models +from django.utils.translation import ugettext_lazy as _, gettext from common.db.models import JMSBaseModel from common.utils import lazyproperty from .permission import Permission -from ..builtin import BuiltinRole from .. import const +from ..builtin import BuiltinRole __all__ = ['Role', 'SystemRole', 'OrgRole'] @@ -33,7 +33,7 @@ class Role(JMSBaseModel): permissions = models.ManyToManyField( 'rbac.Permission', related_name='roles', blank=True, verbose_name=_('Permissions') ) - builtin = models.BooleanField(default=False, verbose_name=_('Built-in')) + builtin = models.BooleanField(default=False, verbose_name=_('Builtin')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) BuiltinRole = BuiltinRole @@ -71,14 +71,14 @@ class Role(JMSBaseModel): @classmethod def get_roles_permissions(cls, roles): org_roles = [role for role in roles if role.scope == cls.Scope.org] - org_perms_id = cls.get_scope_roles_perms(org_roles, cls.Scope.org)\ + org_perms_id = cls.get_scope_roles_perms(org_roles, cls.Scope.org) \ .values_list('id', flat=True) system_roles = [role for role in roles if role.scope == cls.Scope.system] - system_perms_id = cls.get_scope_roles_perms(system_roles, cls.Scope.system)\ + system_perms_id = cls.get_scope_roles_perms(system_roles, cls.Scope.system) \ .values_list('id', flat=True) perms_id = set(org_perms_id) | set(system_perms_id) - permissions = Permission.objects.filter(id__in=perms_id)\ + permissions = Permission.objects.filter(id__in=perms_id) \ .prefetch_related('content_type') return permissions diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index a00814dd3..b30b33f36 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -1,23 +1,22 @@ +import os.path import shutil import zipfile -import yaml -import os.path from typing import Callable -from django.http import HttpResponse +from django.conf import settings from django.core.files.storage import default_storage +from django.http import HttpResponse from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ValidationError -from common.utils import is_uuid from common.drf.serializers import FileSerializer +from common.utils import is_uuid from terminal import serializers from terminal.models import AppletPublication, Applet - __all__ = ['AppletViewSet', 'AppletPublicationViewSet'] @@ -46,17 +45,7 @@ class DownloadUploadMixin: zp.extractall(extract_to) tmp_dir = os.path.join(extract_to, file.name.replace('.zip', '')) - files = ['manifest.yml', 'icon.png', 'i18n.yml', 'setup.yml'] - for name in files: - path = os.path.join(tmp_dir, name) - if not os.path.exists(path): - raise ValidationError({'error': 'Missing file {}'.format(name)}) - - with open(os.path.join(tmp_dir, 'manifest.yml')) as f: - manifest = yaml.safe_load(f) - - if not manifest.get('name', ''): - raise ValidationError({'error': 'Missing name in manifest.yml'}) + manifest = Applet.validate_pkg(tmp_dir) return manifest, tmp_dir @action(detail=False, methods=['post'], serializer_class=FileSerializer) @@ -81,7 +70,10 @@ class DownloadUploadMixin: @action(detail=True, methods=['get']) def download(self, request, *args, **kwargs): instance = self.get_object() - path = default_storage.path('applets/{}'.format(instance.name)) + if instance.builtin: + path = os.path.join(settings.APPS_DIR, 'terminal', 'applets', instance.name) + else: + path = default_storage.path('applets/{}'.format(instance.name)) zip_path = shutil.make_archive(path, 'zip', path) with open(zip_path, 'rb') as f: response = HttpResponse(f.read(), status=200, content_type='application/octet-stream') diff --git a/apps/terminal/applets/__init__.py b/apps/terminal/applets/__init__.py new file mode 100644 index 000000000..bb09feecd --- /dev/null +++ b/apps/terminal/applets/__init__.py @@ -0,0 +1,22 @@ +import os + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def install_or_update_builtin_applets(): + from terminal.models import Applet + + applets = os.listdir(BASE_DIR) + for d in applets: + path = os.path.join(BASE_DIR, d) + if not os.path.isdir(path) or not os.path.exists(os.path.join(path, 'manifest.yml')): + continue + print("Install or update applet: {}".format(path)) + try: + Applet.install_from_dir(path) + except Exception as e: + print(e) + + +if __name__ == '__main__': + install_or_update_builtin_applets() diff --git a/apps/terminal/applets/chrome/README.md b/apps/terminal/applets/chrome/README.md new file mode 100644 index 000000000..068682bfb --- /dev/null +++ b/apps/terminal/applets/chrome/README.md @@ -0,0 +1,7 @@ + +## selenium 版本 + +- Selenium == 4.4.0 +- Chrome 和 ChromeDriver 版本要匹配 +- Driver [下载地址](https://chromedriver.chromium.org/downloads) + diff --git a/apps/terminal/applets/chrome/app.py b/apps/terminal/applets/chrome/app.py new file mode 100644 index 000000000..78e3e2c5a --- /dev/null +++ b/apps/terminal/applets/chrome/app.py @@ -0,0 +1,201 @@ +import time +from enum import Enum +from subprocess import CREATE_NO_WINDOW + +from selenium import webdriver +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.common.by import By +from selenium.webdriver.chrome.service import Service + +from common import (Asset, User, Account, Platform) +from common import (notify_err_message, block_input, unblock_input) +from common import (BaseApplication) + + +class Command(Enum): + TYPE = 'type' + CLICK = 'click' + OPEN = 'open' + + +def _execute_type(ele: WebElement, value: str): + ele.send_keys(value) + + +def _execute_click(ele: WebElement, value: str): + ele.click() + + +commands_func_maps = { + Command.CLICK: _execute_click, + Command.TYPE: _execute_type, + Command.OPEN: _execute_type, +} + + +class StepAction: + methods_map = { + "NAME": By.NAME, + "ID": By.ID, + "CLASS_NAME": By.CLASS_NAME, + "CSS_SELECTOR": By.CSS_SELECTOR, + "CSS": By.CSS_SELECTOR, + "XPATH": By.XPATH + } + + def __init__(self, target='', value='', command=Command.TYPE, **kwargs): + self.target = target + self.value = value + self.command = command + + def execute(self, driver: webdriver.Chrome) -> bool: + if not 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) + if not ele: + return False + if self.command == 'type': + ele.send_keys(self.value) + elif self.command in ['click', 'button']: + ele.click() + elif self.command in ['open']: + driver.get(self.value) + return True + + def _execute_command_type(self, ele, value): + ele.send_keys(value) + + +def execute_action(driver: webdriver.Chrome, step: StepAction) -> bool: + try: + return step.execute(driver) + except Exception as e: + print(e) + notify_err_message(str(e)) + return False + + +class WebAPP(object): + + def __init__(self, app_name: str = '', user: User = None, asset: Asset = None, + account: Account = None, platform: Platform = None, **kwargs): + self.app_name = app_name + self.user = user + self.asset = asset + self.account = account + self.platform = platform + + self.extra_data = self.asset.specific + self._steps = list() + autofill_type = self.asset.specific.autofill + if autofill_type == "basic": + self._steps = self._default_custom_steps() + elif autofill_type == "script": + steps = sorted(self.asset.specific.script, key=lambda step_item: step_item['step']) + for item in steps: + val = item['value'] + if val: + val = val.replace("{USERNAME}", self.account.username) + val = val.replace("{SECRET}", self.account.secret) + item['value'] = val + self._steps.append(item) + + def _default_custom_steps(self) -> list: + account = self.account + specific_property = self.asset.specific + default_steps = [ + { + "step": 1, + "value": account.username, + "target": specific_property.username_selector, + "command": "type" + }, + { + "step": 2, + "value": account.secret, + "target": specific_property.password_selector, + "command": "type" + }, + { + "step": 3, + "value": "", + "target": specific_property.submit_selector, + "command": "click" + } + ] + return default_steps + + def execute(self, driver: webdriver.Chrome) -> bool: + if not self.asset.address: + return True + + for step in self._steps: + action = StepAction(**step) + ret = execute_action(driver, action) + if not ret: + unblock_input() + notify_err_message(f"执行失败: target: {action.target} command: {action.command}") + block_input() + return False + return True + + +def default_chrome_driver_options(): + options = webdriver.ChromeOptions() + options.add_argument("start-maximized") + # 禁用 扩展 + options.add_argument("--disable-extensions") + # 禁用开发者工具 + options.add_argument("--disable-dev-tools") + # 禁用 密码管理器弹窗 + prefs = {"credentials_enable_service": False, + "profile.password_manager_enabled": False} + options.add_experimental_option("prefs", prefs) + options.add_experimental_option("excludeSwitches", ['enable-automation']) + return options + + +class AppletApplication(BaseApplication): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.driver = None + self.app = WebAPP(app_name=self.app_name, user=self.user, + account=self.account, asset=self.asset, platform=self.platform) + self._chrome_options = default_chrome_driver_options() + + def run(self): + service = Service() + # driver 的 console 终端框不显示 + service.creationflags = CREATE_NO_WINDOW + self.driver = webdriver.Chrome(options=self._chrome_options, service=service) + self.driver.implicitly_wait(10) + if self.app.asset.address != "": + self.driver.get(self.app.asset.address) + ok = self.app.execute(self.driver) + if not ok: + print("执行失败") + self.driver.maximize_window() + + def wait(self): + msg = "Unable to evaluate script: disconnected: not connected to DevTools\n" + while True: + time.sleep(5) + logs = self.driver.get_log('driver') + if len(logs) == 0: + continue + ret = logs[-1] + if isinstance(ret, dict): + if ret.get("message") == msg: + print(ret) + break + self.close() + + def close(self): + if self.driver: + try: + self.driver.close() + except Exception as e: + print(e) diff --git a/apps/terminal/applets/chrome/common.py b/apps/terminal/applets/chrome/common.py new file mode 100644 index 000000000..75e59a4c1 --- /dev/null +++ b/apps/terminal/applets/chrome/common.py @@ -0,0 +1,197 @@ +import abc +import subprocess +import sys +import time +import os +import json +import base64 +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 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 = csv_ret.decode() + content_list = content.strip().split("\r\n") + if len(content_list) != 2: + notify_err_message(content) + return False + ret_pid = content_list[1].split(",")[1].strip('"') + return str(pid) == ret_pid + except Exception as e: + notify_err_message(e) + return False + + +def wait_pid(pid): + while 1: + time.sleep(5) + ok = check_pid_alive(pid) + if not ok: + notify_err_message("程序退出") + 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 + + +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 + specific: Specific + + 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 + secret_type: LabelValue + + +class Platform(DictObj): + id: 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/chrome/i18n.yml b/apps/terminal/applets/chrome/i18n.yml new file mode 100644 index 000000000..d91977ba6 --- /dev/null +++ b/apps/terminal/applets/chrome/i18n.yml @@ -0,0 +1,4 @@ +- zh: + display_name: Chrome 浏览器 + comment: 浏览器打开 URL 页面地址 + diff --git a/apps/terminal/applets/chrome/icon.png b/apps/terminal/applets/chrome/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ee35972d8cd847fb5a5825d0d89a2f44dc9c760e GIT binary patch literal 3043 zcmV<93mo)`P)1^@s6$I7^K00006VoOIv0RI60 z0RN!9r;`8x3z$hnK~#90?OS+WLTig? ziyoC$jy)EOrCPUIdu-3)NEJ&}w4%14SSrXWAhHICn1m%EAux#~WM3w;ym{~4{(&Mh z@6F7cHxoHMC*Oa$%lG@u_wMq$zk37pqaXbU0dg~pewCM(M|pm@Ku9VhiU}aZObKbC zK(j^=F2iCe8(m)B`+fV>=VBZmHY{H6^)9EtZ2GY4vf-HJ;N&!|$szBN3u8e6^qHB^Sgjy>J%sLVcpDqxs;-8&sVQo<0Du5g zAmo;!nwn$5+64`r*Um5g@ylZp6U&Fhptz<3j7Zgc#4xbVnuXZevk^0WI#{bUU{jC% z5_jFN4D=vZu`TNh=Mg3VmOM!^^s{C&NO_=bC(yV7;5${>z7ftU&#UHp4*x1peZVzi z)siU>l-jNd0RZn#ONW(C}9BKPBup-oPft)~F08%3OH z5YXB~!3jW|iD2+Rg7H}_O!^?R_v>{)^M^t02ZN`HZ#2w1C>ue`E}$2Z%zyM30sy*9 zT5Oq^iX{i@0~%xWdJKK-HCZ8e0oeAX7ax{-(dLj}j9CVlU}N#%3_X%#WkebLFyL+g zx`S>E0ihqnrnvq)HPg7@V4cKwxej%MOfv(KZ7E4br#WyED`n+Mn8%Kll-IY3cw~bM zf8WOi6aoMQ5jb)|zyohM@!@f=ObY@GeG<^b1>^_{hQQOxuavFwFL-xOQA%~HB_>roe@?5g1L(FvImlJG-7 zKUfu~HbIk4sdT-R{OUG~c19>No+Ws9dWNroq$I@7nImbtcDDx|y%7X!0Ny;np|!`4 zgJ^(w*$*5NtdX#uoilR%%Ozs8T6Eb;2=|XjKwXmg`ronh<^hZ^UAbMr$&0~v9wngk zny3<3C#mM-Tig;QC1Ug#H3|M@au5l@Tep5UIxLf{yS!+;Acp^Ivl^NI8T4RB4s)JIXv^Mflah(r9WxWiaNiy0Mfe=U{NFOmM zz_|4?0Y(t~W9AS*!$O7przdbzEcHL2nvT3GzcB%^?(;s1!Yr%K}vZ6DmLndQQCs&Xod zRHCep7;rvb7O z{oJJj(0f)DpWqfR`j<(>h<9*T)sXM3FO!trQKW@FN)HnjPt^IxbU<$fz;~eXRN?& z>4Ej60Gc{Qv^ywVULaurL6Vigs0$LT2~ME)3843!D!%~0an^m#vgF8cxjNaXXwHes z`bpDpxA$<0(M6WCM4y;sF9`#m7<~bB&?Tz3#bc$uES;L+GCa; z`Dvi~fbsJE`%dkiR@q{Y$U1;cwTCcq%Sx=R*ajCL@S0N=Cz>zdp}o%{wSKkg;Oa)W z?c5)N)d&6TA51Hp^4#4y2i2|}{;=vym_KAZW(*#QqQq>Zn0(XyE}lbW`(^BI_!2wq zWw_9F1$znzMzX3O@Lh+q*3)QSbU5hqtB`Zx@t;roY~!?yTO;;H21bnz@rD?%gh8*@ zjW%}=D85S}%#CBQAwA;336Y~{-Pe0d((g)-25T2mjPi_(xTk+Mvf*gnKy_IZm0$s! zPHRHqz{pdFoyVLX#VtQ}cc^wD&)B2v_Kt-on|>7U;G&84)5~PT8 zV)ZB&2&0~#cL=oOK=(a^o+{fOPQS3ywgeFA`-i3q46}9PjP%>XeaWK)ubNvV_qE}G z?-YdAeZ47~R5Vt3F~y25=(&nb8JajJna*UFN#bvd5X9{0eNqk2u&H`}pbX87NRyXxgTj=NtmFHBv3sx@4HS0DOI6AJ9>s73yCW%u1ek4QU9ou{}eerf6(Ws#ld{e6ih*^St8 z%cxtw7#6p>CN*aA=^-&XPAmV_@2hRbOSf|%n!v7z9*W*7Pph+z`&U<4_p^DQR7Yhb z_@mGNw`>!@8cN1aE*olEa@DGz^7TMtTB}uWYBy^MB?O1{cElHcH!7%?!qdbH-Zs9A z>l80~zve&bDtEjwY){pdzV`K|{G;84>}-7(kjAiLT;k-ITv4Db?-ts52W{cbI4_T^ ltBWj)(~o}iqaWV`{s(sR0GInw;`RUl002ovPDHLkV1iJjw?qH{ literal 0 HcmV?d00001 diff --git a/apps/terminal/applets/chrome/main.py b/apps/terminal/applets/chrome/main.py new file mode 100644 index 000000000..be0ff3585 --- /dev/null +++ b/apps/terminal/applets/chrome/main.py @@ -0,0 +1,22 @@ +import sys + +from common import (block_input, unblock_input) +from common import convert_base64_to_dict +from app import AppletApplication + + +def main(): + base64_str = sys.argv[1] + data = convert_base64_to_dict(base64_str) + applet_app = AppletApplication(**data) + block_input() + applet_app.run() + unblock_input() + applet_app.wait() + + +if __name__ == '__main__': + try: + main() + except Exception as e: + print(e) diff --git a/apps/terminal/applets/chrome/manifest.yml b/apps/terminal/applets/chrome/manifest.yml new file mode 100644 index 000000000..f2681a0c2 --- /dev/null +++ b/apps/terminal/applets/chrome/manifest.yml @@ -0,0 +1,12 @@ +name: chrome +display_name: Chrome Browser +version: 0.1 +comment: Chrome Browser Open URL Page Address +author: JumpServer Team +exec_type: python +update_policy: always +type: web +tags: + - web +protocols: + - http diff --git a/apps/terminal/applets/chrome/setup.yml b/apps/terminal/applets/chrome/setup.yml new file mode 100644 index 000000000..9b9a950f1 --- /dev/null +++ b/apps/terminal/applets/chrome/setup.yml @@ -0,0 +1,6 @@ +type: manual # exe, zip, manual +source: +arguments: +destination: +program: +md5: diff --git a/apps/terminal/applets/chrome/test_data_example.json b/apps/terminal/applets/chrome/test_data_example.json new file mode 100644 index 000000000..fc8e00991 --- /dev/null +++ b/apps/terminal/applets/chrome/test_data_example.json @@ -0,0 +1,41 @@ +{ + "protocol": "web", + "user": { + "id": "2647CA35-5CAD-4DDF-8A88-6BD88F39BB30", + "name": "Administrator", + "username": "admin" + }, + "asset": { + "id": "46EE5F50-F1C1-468C-97EE-560E3436754C", + "name": "test_baidu", + "address": "https://www.baidu.com", + "category": { + "value": "web", + "label": "web" + }, + "protocols": [ + { + "id": 2, + "name": "http", + "port": 80 + } + ], + "specific": { + "autofill": "basic", + "username_selector": "name=username", + "password_selector": "name=password", + "submit_selector": "id=longin_button", + "script": [] + }, + "org_id": "2925D985-A435-411D-9BC4-FEA630F105D9" + }, + "account": { + "id": "9D5585DE-5132-458C-AABE-89A83C112A83", + "name": "test_mysql", + "username": "root", + "secret": "" + }, + "platform": { + "charset": "UTF-8" + } +} diff --git a/apps/terminal/management/__init__.py b/apps/terminal/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/terminal/management/commands/__init__.py b/apps/terminal/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/terminal/management/commands/install_builtin_applets.py b/apps/terminal/management/commands/install_builtin_applets.py new file mode 100644 index 000000000..d3909d16b --- /dev/null +++ b/apps/terminal/management/commands/install_builtin_applets.py @@ -0,0 +1,9 @@ +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = 'Install builtin applets' + + def handle(self, *args, **options): + from terminal.applets import install_or_update_builtin_applets + install_or_update_builtin_applets() diff --git a/apps/terminal/migrations/0063_applet_builtin.py b/apps/terminal/migrations/0063_applet_builtin.py new file mode 100644 index 000000000..1d991e180 --- /dev/null +++ b/apps/terminal/migrations/0063_applet_builtin.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-12-20 07:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0062_auto_20221216_1529'), + ] + + operations = [ + migrations.AddField( + model_name='applet', + name='builtin', + field=models.BooleanField(default=False, verbose_name='Builtin'), + ), + ] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index bfe0e5e67..9c712b6e5 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -7,6 +7,7 @@ from django.core.cache import cache from django.core.files.storage import default_storage from django.db import models from django.utils.translation import gettext_lazy as _ +from rest_framework.serializers import ValidationError from common.db.models import JMSBaseModel @@ -24,6 +25,7 @@ class Applet(JMSBaseModel): author = models.CharField(max_length=128, verbose_name=_('Author')) type = models.CharField(max_length=16, verbose_name=_('Type'), default='general', choices=Type.choices) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) + builtin = models.BooleanField(default=False, verbose_name=_('Builtin')) protocols = models.JSONField(default=list, verbose_name=_('Protocol')) tags = models.JSONField(default=list, verbose_name=_('Tags')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) @@ -37,7 +39,10 @@ class Applet(JMSBaseModel): @property def path(self): - return default_storage.path('applets/{}'.format(self.name)) + if self.builtin: + return os.path.join(settings.APPS_DIR, 'terminal', 'applets', self.name) + else: + return default_storage.path('applets/{}'.format(self.name)) @property def manifest(self): @@ -54,6 +59,33 @@ class Applet(JMSBaseModel): return None return os.path.join(settings.MEDIA_URL, 'applets', self.name, 'icon.png') + @staticmethod + def validate_pkg(d): + files = ['manifest.yml', 'icon.png', 'i18n.yml', 'setup.yml'] + for name in files: + path = os.path.join(d, name) + if not os.path.exists(path): + raise ValidationError({'error': 'Missing file {}'.format(path)}) + + with open(os.path.join(d, 'manifest.yml')) as f: + manifest = yaml.safe_load(f) + + if not manifest.get('name', ''): + raise ValidationError({'error': 'Missing name in manifest.yml'}) + return manifest + + @classmethod + def install_from_dir(cls, path): + from terminal.serializers import AppletSerializer + + manifest = cls.validate_pkg(path) + name = manifest['name'] + instance = cls.objects.filter(name=name).first() + serializer = AppletSerializer(instance=instance, data=manifest) + serializer.is_valid() + serializer.save(builtin=True) + return instance + def select_host_account(self): hosts = list(self.hosts.all()) if not hosts: @@ -73,6 +105,7 @@ class Applet(JMSBaseModel): ttl = 60 * 60 * 24 lock_key = 'applet_host_accounts_{}_{}'.format(host.id, account.username) cache.set(lock_key, account.username, ttl) + return { 'host': host, 'account': account, diff --git a/jms b/jms index 2bb8f64c4..91021767e 100755 --- a/jms +++ b/jms @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # coding: utf-8 -import os +import argparse import logging import logging.handlers -import time -import argparse +import os import sys +import time + import django -import requests from django.core import management from django.db.utils import OperationalError @@ -24,6 +24,7 @@ logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s", datef try: from jumpserver import const + __version__ = const.VERSION except ImportError as e: print("Not found __version__: {}".format(e)) @@ -122,6 +123,14 @@ def download_ip_db(): download_file(src, path) +def install_builtin_applets(): + logging.info("Install builtin applets") + try: + management.call_command('install_builtin_applets', verbosity=0, interactive=False) + except: + pass + + def upgrade_db(): collect_static() perform_db_migrate() @@ -132,6 +141,7 @@ def prepare(): upgrade_db() expire_caches() download_ip_db() + install_builtin_applets() def start_services(): @@ -190,4 +200,3 @@ if __name__ == '__main__': collect_static() else: start_services() - From e4b4f983626a0bb3d79d5100ad52af1928c31dca Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 20 Dec 2022 17:28:27 +0800 Subject: [PATCH 063/132] =?UTF-8?q?feat:=20=20=E4=BD=9C=E4=B8=9A=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E6=B7=BB=E5=8A=A0=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 7ce6d7894..5e74e0aad 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -20,7 +20,7 @@ from .const import ( class JobAuditLogSerializer(JobExecutionSerializer): class Meta: model = JobAuditLog - read_only_fields = ["timedelta", "time_cost", 'is_finished', 'date_start', + read_only_fields = ["id", "material", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_finished', 'date_created', 'is_success', From 0748c32c5a67a365a3a67944fc3fbd34d8649448 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Dec 2022 17:52:08 +0800 Subject: [PATCH 064/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20session=20?= =?UTF-8?q?type=20=E5=BA=8F=E5=88=97=F0=9F=A5=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/base.py | 2 +- apps/terminal/api/applet/applet.py | 1 + apps/terminal/serializers/session.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 0ce5e9da3..71c641e71 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -35,7 +35,7 @@ def parse_sentinels_host(sentinels_host): VERSION = const.VERSION BASE_DIR = const.BASE_DIR PROJECT_DIR = const.PROJECT_DIR -APP_DIR = os.path.join(PROJECT_DIR, 'apps') +APPS_DIR = os.path.join(PROJECT_DIR, 'apps') DATA_DIR = os.path.join(PROJECT_DIR, 'data') ANSIBLE_DIR = os.path.join(DATA_DIR, 'ansible') CERTS_DIR = os.path.join(DATA_DIR, 'certs') diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index b30b33f36..fddf90ad1 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -78,6 +78,7 @@ class DownloadUploadMixin: with open(zip_path, 'rb') as f: response = HttpResponse(f.read(), status=200, content_type='application/octet-stream') response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}.zip'.format(instance.name) + os.unlink(zip_path) return response diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py index 517262e0f..184262ec4 100644 --- a/apps/terminal/serializers/session.py +++ b/apps/terminal/serializers/session.py @@ -22,7 +22,7 @@ class SessionType(models.TextChoices): class SessionSerializer(BulkOrgResourceModelSerializer): org_id = serializers.CharField(allow_blank=True) protocol = serializers.ChoiceField(choices=Protocol.choices, label=_("Protocol")) - type = LabeledChoiceField(choices=SessionType.choices, label=_("Type")) + type = LabeledChoiceField(choices=SessionType.choices, label=_("Type"), default=SessionType.normal) class Meta: model = Session From a7815dc9e510831e236717882b4e687e831e9a41 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Dec 2022 18:12:35 +0800 Subject: [PATCH 065/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20job=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0017_auto_20221220_1757.py | 23 ++++++++++++++++ apps/audits/models.py | 6 ++--- apps/audits/serializers.py | 26 +++++++------------ 3 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 apps/audits/migrations/0017_auto_20221220_1757.py diff --git a/apps/audits/migrations/0017_auto_20221220_1757.py b/apps/audits/migrations/0017_auto_20221220_1757.py new file mode 100644 index 000000000..b879648e8 --- /dev/null +++ b/apps/audits/migrations/0017_auto_20221220_1757.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-12-20 09:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('audits', '0016_auto_20221111_1919'), + ] + + operations = [ + migrations.RenameField( + model_name='ftplog', + old_name='system_user', + new_name='account', + ), + migrations.AlterField( + model_name='ftplog', + name='account', + field=models.CharField(default='', max_length=128, verbose_name='Account'), + preserve_default=False, + ), + ] diff --git a/apps/audits/models.py b/apps/audits/models.py index 1e65ea233..5f11fca9f 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -2,11 +2,11 @@ import uuid from django.db import models from django.db.models import Q -from django.utils.translation import gettext, ugettext_lazy as _ from django.utils import timezone +from django.utils.translation import gettext, ugettext_lazy as _ -from common.utils import lazyproperty from common.db.encoder import ModelJSONFieldEncoder +from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, Organization from orgs.utils import current_org from .const import ( @@ -32,7 +32,7 @@ class FTPLog(OrgModelMixin): max_length=128, verbose_name=_("Remote addr"), blank=True, null=True ) asset = models.CharField(max_length=1024, verbose_name=_("Asset")) - system_user = models.CharField(max_length=128, verbose_name=_("System user")) + account = models.CharField(max_length=128, verbose_name=_("Account")) operate = models.CharField( max_length=16, verbose_name=_("Operate"), choices=OperateChoices.choices ) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 5e74e0aad..fd803184c 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -9,10 +9,8 @@ from ops.serializers.job import JobExecutionSerializer from terminal.models import Session from . import models from .const import ( - ActionChoices, - OperateChoices, - MFAChoices, - LoginStatusChoices, + ActionChoices, OperateChoices, + MFAChoices, LoginStatusChoices, LoginTypeChoices, ) @@ -20,11 +18,11 @@ from .const import ( class JobAuditLogSerializer(JobExecutionSerializer): class Meta: model = JobAuditLog - read_only_fields = ["id", "material", "timedelta", "time_cost", 'is_finished', 'date_start', - 'date_finished', - 'date_created', - 'is_success', - 'creator_name'] + read_only_fields = [ + "id", "material", "time_cost", + 'date_start', 'date_finished', 'date_created', + 'is_finished', 'is_success', 'creator_by' + ] fields = read_only_fields + [] @@ -35,14 +33,8 @@ class FTPLogSerializer(serializers.ModelSerializer): model = models.FTPLog fields_mini = ["id"] fields_small = fields_mini + [ - "user", - "remote_addr", - "asset", - "system_user", - "org_id", - "operate", - "filename", - "is_success", + "user", "remote_addr", "asset", "account", + "org_id", "operate", "filename", "is_success", "date_start", ] fields = fields_small From 55cee43f91ddedd6bc0af4a63056110b18e6cf57 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Tue, 20 Dec 2022 18:37:28 +0800 Subject: [PATCH 066/132] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96Sentinels?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 71c641e71..d1fb7ba9a 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -343,9 +343,9 @@ if REDIS_SENTINEL_SERVICE_NAME and REDIS_SENTINELS: } }) if REDIS_USE_SSL: - REDIS_OPTIONS['CONNECTION_POOL_KWARGS'].update({ - 'connection_class': SentinelManagedSSLConnection - }) + CONNECTION_POOL_KWARGS = REDIS_OPTIONS['CONNECTION_POOL_KWARGS'] + CONNECTION_POOL_KWARGS['connection_class'] = SentinelManagedSSLConnection + REDIS_OPTIONS['CONNECTION_POOL_KWARGS'] = CONNECTION_POOL_KWARGS DJANGO_REDIS_CONNECTION_FACTORY = 'django_redis.pool.SentinelConnectionFactory' else: REDIS_LOCATION_NO_DB = '%(protocol)s://:%(password)s@%(host)s:%(port)s/{}' % { From 6e467d9b67f2caa3708566997afd4206e78a9098 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 20 Dec 2022 18:50:04 +0800 Subject: [PATCH 067/132] perf: terminal connect method gui (#9224) Co-authored-by: feng <1304903146@qq.com> --- apps/terminal/connect_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/connect_methods.py b/apps/terminal/connect_methods.py index 7b17dbe4e..10bf30c81 100644 --- a/apps/terminal/connect_methods.py +++ b/apps/terminal/connect_methods.py @@ -31,7 +31,7 @@ class WebMethod(TextChoices): Protocol.redis: [cls.web_cli], Protocol.mongodb: [cls.web_cli], - Protocol.k8s: [cls.web_gui], + Protocol.k8s: [cls.web_cli], Protocol.http: [] } From 21d6243b61c2e6e36f698df73a9df342a0f015cc Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 20 Dec 2022 19:18:39 +0800 Subject: [PATCH 068/132] perf: api doc --- apps/audits/api.py | 2 +- apps/perms/filters.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 990d4353a..b060a0eb1 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -34,7 +34,7 @@ class FTPLogViewSet(CreateModelMixin, ListModelMixin, OrgGenericViewSet): date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] - filterset_fields = ['user', 'asset', 'system_user', 'filename'] + filterset_fields = ['user', 'asset', 'account', 'filename'] search_fields = filterset_fields ordering = ['-date_start'] diff --git a/apps/perms/filters.py b/apps/perms/filters.py index e64e919ea..05f202655 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -13,15 +13,15 @@ class PermissionBaseFilter(BaseFilterSet): is_valid = filters.BooleanFilter(method='do_nothing') user_id = filters.UUIDFilter(method='do_nothing') username = filters.CharFilter(method='do_nothing') - system_user_id = filters.UUIDFilter(method='do_nothing') - system_user = filters.CharFilter(method='do_nothing') + account_id = filters.UUIDFilter(method='do_nothing') + account = filters.CharFilter(method='do_nothing') user_group_id = filters.UUIDFilter(method='do_nothing') user_group = filters.CharFilter(method='do_nothing') all = filters.BooleanFilter(method='do_nothing') class Meta: fields = ( - 'user_id', 'username', 'system_user_id', 'system_user', + 'user_id', 'username', 'account_id', 'account', 'user_group_id', 'user_group', 'name', 'all', 'is_valid', ) From 0c35205e31237297b01934fbd4e4b54e7782e8c2 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 20 Dec 2022 19:46:19 +0800 Subject: [PATCH 069/132] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E7=89=88=E6=9C=AC=E5=8E=86=E5=8F=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/job.py | 3 +- .../ops/migrations/0030_auto_20221220_1941.py | 80 +++++++++++++++++++ apps/ops/models/job.py | 50 +++++++----- apps/ops/signal_handlers.py | 9 ++- 4 files changed, 122 insertions(+), 20 deletions(-) create mode 100644 apps/ops/migrations/0030_auto_20221220_1941.py diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index a49b1af90..9b36a5aba 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -3,7 +3,6 @@ from django.shortcuts import get_object_or_404 from rest_framework.response import Response from ops.models import Job, JobExecution -from ops.models.job import JobAuditLog from ops.serializers.job import JobSerializer, JobExecutionSerializer __all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobAssetDetail', ] @@ -58,6 +57,8 @@ class JobExecutionViewSet(OrgBulkModelViewSet): def perform_create(self, serializer): instance = serializer.save() + instance.job_version = instance.job.version + instance.save() task = run_ops_job_execution.delay(instance.id) set_task_to_serializer_data(serializer, task) diff --git a/apps/ops/migrations/0030_auto_20221220_1941.py b/apps/ops/migrations/0030_auto_20221220_1941.py new file mode 100644 index 000000000..cebbd8aa1 --- /dev/null +++ b/apps/ops/migrations/0030_auto_20221220_1941.py @@ -0,0 +1,80 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:41 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ops', '0029_auto_20221215_1712'), + ] + + operations = [ + migrations.CreateModel( + name='JobAuditLog', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('ops.jobexecution',), + ), + migrations.AddField( + model_name='job', + name='version', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='jobexecution', + name='job_version', + field=models.IntegerField(default=0), + ), + migrations.CreateModel( + name='HistoricalJob', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(blank=True, editable=False, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')), + ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), + ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), + ('name', models.CharField(max_length=128, null=True, verbose_name='Name')), + ('instant', models.BooleanField(default=False)), + ('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')), + ('module', models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, null=True, verbose_name='Module')), + ('chdir', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Chdir')), + ('timeout', models.IntegerField(default=60, verbose_name='Timeout (Seconds)')), + ('type', models.CharField(choices=[('adhoc', 'Adhoc'), ('playbook', 'Playbook')], default='adhoc', max_length=128, verbose_name='Type')), + ('runas', models.CharField(default='root', max_length=128, verbose_name='Runas')), + ('runas_policy', models.CharField(choices=[('privileged_only', 'Privileged Only'), ('privileged_first', 'Privileged First'), ('skip', 'Skip')], default='skip', max_length=128, verbose_name='Runas policy')), + ('use_parameter_define', models.BooleanField(default=False, verbose_name='Use Parameter Define')), + ('parameters_define', models.JSONField(default=dict, verbose_name='Parameters define')), + ('comment', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment')), + ('version', models.IntegerField(default=0)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('creator', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('playbook', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='ops.playbook', verbose_name='Playbook')), + ], + options={ + 'verbose_name': 'historical job', + 'verbose_name_plural': 'historical jobs', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 4f003895e..bdd00382f 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -11,6 +11,8 @@ from celery import current_task __all__ = ["Job", "JobExecution", "JobAuditLog"] +from simple_history.models import HistoricalRecords + from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.mixin import PeriodTaskModelMixin from ops.variables import * @@ -37,6 +39,11 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): use_parameter_define = models.BooleanField(default=False, verbose_name=(_('Use Parameter Define'))) parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define')) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) + version = models.IntegerField(default=0) + history = HistoricalRecords() + + def get_history(self, version): + return self.history.filter(version=version).first() @property def last_execution(self): @@ -79,7 +86,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): return JMSInventory(self.assets.all(), self.runas_policy, self.runas) def create_execution(self): - return self.executions.create() + return self.executions.create(job_version=self.version) class Meta: ordering = ['date_created'] @@ -90,6 +97,7 @@ class JobExecution(JMSOrgBaseModel): task_id = models.UUIDField(null=True) status = models.CharField(max_length=16, verbose_name=_('Status'), default=JobStatus.running) job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='executions', null=True) + job_version = models.IntegerField(default=0) parameters = models.JSONField(default=dict, verbose_name=_('Parameters')) result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) summary = models.JSONField(default=dict, verbose_name=_('Summary')) @@ -98,12 +106,18 @@ 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")) + @property + def current_job(self): + if self.job.version != self.job_version: + return self.job.get_history(self.job_version) + return self.job + @property def material(self): - if self.job.type == 'adhoc': - return "{}:{}".format(self.job.module, self.job.args) - if self.job.type == 'playbook': - return "{}:{}:{}".format(self.org.name, self.job.creator.name, self.job.playbook.name) + if self.current_job.type == 'adhoc': + return "{}:{}".format(self.current_job.module, self.current_job.args) + if self.current_job.type == 'playbook': + return "{}:{}:{}".format(self.org.name, self.current_job.creator.name, self.current_job.playbook.name) @property def assent_result_detail(self): @@ -112,7 +126,7 @@ class JobExecution(JMSOrgBaseModel): "summary": self.count, "detail": [], } - for asset in self.job.assets.all(): + for asset in self.current_job.assets.all(): asset_detail = { "name": asset.name, "status": "ok", @@ -145,17 +159,17 @@ class JobExecution(JMSOrgBaseModel): @property def job_type(self): - return self.job.type + return self.current_job.type def compile_shell(self): - if self.job.type != 'adhoc': + if self.current_job.type != 'adhoc': return - result = "{}{}{} ".format('\'', self.job.args, '\'') - result += "chdir={}".format(self.job.chdir) + result = "{}{}{} ".format('\'', self.current_job.args, '\'') + result += "chdir={}".format(self.current_job.chdir) return result def get_runner(self): - inv = self.job.inventory + inv = self.current_job.inventory inv.write_to_file(self.inventory_path) self.summary = self.result = {"excludes": {}} if len(inv.exclude_hosts) > 0: @@ -171,15 +185,15 @@ class JobExecution(JMSOrgBaseModel): static_variables = self.gather_static_variables() extra_vars.update(static_variables) - if self.job.type == 'adhoc': + if self.current_job.type == 'adhoc': args = self.compile_shell() runner = AdHocRunner( - self.inventory_path, self.job.module, module_args=args, + self.inventory_path, self.current_job.module, module_args=args, pattern="all", project_dir=self.private_dir, extra_vars=extra_vars, ) - elif self.job.type == 'playbook': + elif self.current_job.type == 'playbook': runner = PlaybookRunner( - self.inventory_path, self.job.playbook.entry + self.inventory_path, self.current_job.playbook.entry ) else: raise Exception("unsupported job type") @@ -187,8 +201,8 @@ class JobExecution(JMSOrgBaseModel): def gather_static_variables(self): default = { - JMS_JOB_ID: str(self.job.id), - JMS_JOB_NAME: self.job.name, + JMS_JOB_ID: str(self.current_job.id), + JMS_JOB_NAME: self.current_job.name, } if self.creator: default.update({JMS_USERNAME: self.creator.username}) @@ -225,7 +239,7 @@ class JobExecution(JMSOrgBaseModel): @property def private_dir(self): uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id - job_name = self.job.name if self.job.name else 'instant' + job_name = self.current_job.name if self.current_job.name else 'instant' return os.path.join(settings.ANSIBLE_DIR, job_name, uniq) def set_error(self, error): diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index 965bd494c..b1d2c39f3 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -3,6 +3,7 @@ from celery import signals from django.db import transaction from django.core.cache import cache +from django.db.models.signals import pre_save from django.dispatch import receiver from django.db.utils import ProgrammingError from django.utils import translation, timezone @@ -12,7 +13,7 @@ from common.signals import django_ready from common.db.utils import close_old_connections, get_logger from .celery import app -from .models import CeleryTaskExecution, CeleryTask +from .models import CeleryTaskExecution, CeleryTask, Job logger = get_logger(__name__) @@ -20,6 +21,12 @@ TASK_LANG_CACHE_KEY = 'TASK_LANG_{}' TASK_LANG_CACHE_TTL = 1800 +@receiver(pre_save, sender=Job) +def on_account_pre_create(sender, instance, **kwargs): + # 升级版本号 + instance.version += 1 + + @receiver(django_ready) def sync_registered_tasks(*args, **kwargs): with transaction.atomic(): From 58909ee67de5c9c346ab774985738862dc8214c0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Dec 2022 20:23:42 +0800 Subject: [PATCH 070/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=20model=EF=BC=8C=E7=BB=A7=E6=89=BF=E5=90=8C=E4=B8=80?= =?UTF-8?q?=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0006_commandfilteracl_commandgroup.py | 4 +- .../migrations/0008_commandgroup_comment.py | 27 ++- .../migrations/0009_auto_20221204_0001.py | 22 --- .../migrations/0009_auto_20221220_1956.py | 53 +++++ .../migrations/0010_auto_20221205_1122.py | 25 --- apps/acls/models/base.py | 7 +- apps/acls/models/command_acl.py | 2 - .../0010_appaccount_historicalappaccount.py | 64 +++--- .../migrations/0027_auto_20221220_1956.py | 28 +++ apps/applications/models.py | 8 +- .../migrations/0099_auto_20220711_1409.py | 54 ++++-- .../migrations/0115_auto_20221220_1956.py | 183 ++++++++++++++++++ apps/assets/models/asset/common.py | 6 +- apps/assets/models/automations/base.py | 15 +- apps/assets/models/backup.py | 11 +- apps/assets/models/base.py | 2 - apps/assets/models/cmd_filter.py | 5 - apps/assets/models/domain.py | 11 +- apps/assets/models/favorite_asset.py | 5 +- apps/assets/models/gathered_user.py | 11 +- apps/assets/models/label.py | 1 - apps/assets/models/node.py | 21 +- apps/audits/serializers.py | 6 +- .../migrations/0016_auto_20221220_1956.py | 58 ++++++ .../authentication/models/connection_token.py | 5 +- apps/common/db/models.py | 5 +- apps/common/mixins/models.py | 15 +- .../migrations/0003_auto_20221220_1956.py | 53 +++++ apps/notifications/models/notification.py | 2 + apps/notifications/models/site_msg.py | 8 +- .../ops/migrations/0026_auto_20221009_2050.py | 32 +-- .../ops/migrations/0031_auto_20221220_1956.py | 58 ++++++ apps/ops/models/job.py | 7 +- apps/orgs/migrations/0001_initial.py | 6 +- .../migrations/0015_auto_20221220_1956.py | 28 +++ apps/orgs/models.py | 9 +- .../migrations/0034_auto_20221220_1956.py | 54 ++++++ apps/perms/models/__init__.py | 1 - apps/perms/models/asset_permission.py | 23 +-- apps/perms/models/perm_node.py | 9 +- apps/perms/models/perm_token.py | 21 -- .../migrations/0010_auto_20221220_1956.py | 38 ++++ .../0016_commandstorage_replaystorage.py | 4 +- .../migrations/0064_auto_20221220_1956.py | 178 +++++++++++++++++ apps/terminal/models/component/storage.py | 14 +- apps/terminal/models/component/task.py | 9 +- apps/terminal/models/component/terminal.py | 7 +- apps/terminal/models/session/replay.py | 6 +- apps/terminal/models/session/sharing.py | 17 +- .../migrations/0020_auto_20220817_1346.py | 6 +- .../migrations/0025_auto_20221206_1820.py | 8 +- .../migrations/0026_auto_20221220_1956.py | 98 ++++++++++ apps/tickets/models/comment.py | 6 +- apps/tickets/models/flow.py | 9 +- apps/tickets/models/ticket/general.py | 21 +- apps/users/migrations/0001_initial.py | 48 +++-- .../migrations/0041_auto_20221220_1956.py | 33 ++++ apps/users/models/group.py | 12 +- 58 files changed, 1135 insertions(+), 344 deletions(-) delete mode 100644 apps/acls/migrations/0009_auto_20221204_0001.py create mode 100644 apps/acls/migrations/0009_auto_20221220_1956.py delete mode 100644 apps/acls/migrations/0010_auto_20221205_1122.py create mode 100644 apps/applications/migrations/0027_auto_20221220_1956.py create mode 100644 apps/assets/migrations/0115_auto_20221220_1956.py create mode 100644 apps/authentication/migrations/0016_auto_20221220_1956.py create mode 100644 apps/notifications/migrations/0003_auto_20221220_1956.py create mode 100644 apps/ops/migrations/0031_auto_20221220_1956.py create mode 100644 apps/orgs/migrations/0015_auto_20221220_1956.py create mode 100644 apps/perms/migrations/0034_auto_20221220_1956.py delete mode 100644 apps/perms/models/perm_token.py create mode 100644 apps/rbac/migrations/0010_auto_20221220_1956.py create mode 100644 apps/terminal/migrations/0064_auto_20221220_1956.py create mode 100644 apps/tickets/migrations/0026_auto_20221220_1956.py create mode 100644 apps/users/migrations/0041_auto_20221220_1956.py diff --git a/apps/acls/migrations/0006_commandfilteracl_commandgroup.py b/apps/acls/migrations/0006_commandfilteracl_commandgroup.py index 95b12e9f0..cc91c998e 100644 --- a/apps/acls/migrations/0006_commandfilteracl_commandgroup.py +++ b/apps/acls/migrations/0006_commandfilteracl_commandgroup.py @@ -28,6 +28,7 @@ class Migration(migrations.Migration): ('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command', max_length=16, verbose_name='Type')), ('content', models.TextField(help_text='One line one command', verbose_name='Content')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('ignore_case', models.BooleanField(default=True, verbose_name='Ignore case')), ], options={ @@ -57,7 +58,8 @@ class Migration(migrations.Migration): ('assets', models.JSONField(verbose_name='Asset')), ('commands', models.ManyToManyField(to='acls.CommandGroup', verbose_name='Commands')), ( - 'reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), + 'reviewers', + models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), ], options={ 'verbose_name': 'Command acl', diff --git a/apps/acls/migrations/0008_commandgroup_comment.py b/apps/acls/migrations/0008_commandgroup_comment.py index 631ff8eb7..82e44912c 100644 --- a/apps/acls/migrations/0008_commandgroup_comment.py +++ b/apps/acls/migrations/0008_commandgroup_comment.py @@ -1,18 +1,33 @@ # Generated by Django 3.2.14 on 2022-12-02 04:25 -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): - dependencies = [ ('acls', '0007_auto_20221202_1048'), ] operations = [ - migrations.AddField( - model_name='commandgroup', - name='comment', - field=models.TextField(blank=True, verbose_name='Comment'), + migrations.AlterModelOptions( + name='commandgroup', + options={'verbose_name': 'Command group'}, + ), + migrations.RenameField( + model_name='commandfilteracl', + old_name='commands', + new_name='command_groups', + ), + migrations.AlterModelOptions( + name='commandfilteracl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Command acl'}, + ), + migrations.AlterModelOptions( + name='loginacl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Login acl'}, + ), + migrations.AlterModelOptions( + name='loginassetacl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Login asset acl'}, ), ] diff --git a/apps/acls/migrations/0009_auto_20221204_0001.py b/apps/acls/migrations/0009_auto_20221204_0001.py deleted file mode 100644 index b5286160f..000000000 --- a/apps/acls/migrations/0009_auto_20221204_0001.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.14 on 2022-12-03 16:01 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('acls', '0008_commandgroup_comment'), - ] - - operations = [ - migrations.AlterModelOptions( - name='commandgroup', - options={'verbose_name': 'Command group'}, - ), - migrations.RenameField( - model_name='commandfilteracl', - old_name='commands', - new_name='command_groups', - ), - ] diff --git a/apps/acls/migrations/0009_auto_20221220_1956.py b/apps/acls/migrations/0009_auto_20221220_1956.py new file mode 100644 index 000000000..78f8b1152 --- /dev/null +++ b/apps/acls/migrations/0009_auto_20221220_1956.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0008_commandgroup_comment'), + ] + + operations = [ + migrations.AddField( + model_name='commandfilteracl', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='loginacl', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='loginassetacl', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='commandfilteracl', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='commandgroup', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='commandgroup', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='loginacl', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='loginassetacl', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + ] diff --git a/apps/acls/migrations/0010_auto_20221205_1122.py b/apps/acls/migrations/0010_auto_20221205_1122.py deleted file mode 100644 index 78adde93b..000000000 --- a/apps/acls/migrations/0010_auto_20221205_1122.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 3.2.14 on 2022-12-05 03:22 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('acls', '0009_auto_20221204_0001'), - ] - - operations = [ - migrations.AlterModelOptions( - name='commandfilteracl', - options={'ordering': ('priority', 'name'), 'verbose_name': 'Command acl'}, - ), - migrations.AlterModelOptions( - name='loginacl', - options={'ordering': ('priority', 'name'), 'verbose_name': 'Login acl'}, - ), - migrations.AlterModelOptions( - name='loginassetacl', - options={'ordering': ('priority', 'name'), 'verbose_name': 'Login asset acl'}, - ), - ] diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 704e3d743..256241361 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -3,7 +3,7 @@ from django.db import models from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from common.mixins import CommonModelMixin +from common.db.models import JMSBaseModel from common.utils import contains_ip from orgs.mixins.models import OrgModelMixin @@ -58,7 +58,7 @@ class UserAssetAccountACLQuerySet(BaseACLQuerySet): def filter_account(self, username): q = Q(accounts__username_group__contains=username) | \ - Q(accounts__username_group__contains='*') + Q(accounts__username_group__contains='*') return self.filter(q) @@ -67,7 +67,7 @@ class ACLManager(models.Manager): return self.get_queryset().valid() -class BaseACL(CommonModelMixin): +class BaseACL(JMSBaseModel): name = models.CharField(max_length=128, verbose_name=_('Name')) priority = models.IntegerField( default=50, verbose_name=_("Priority"), @@ -77,7 +77,6 @@ class BaseACL(CommonModelMixin): action = models.CharField(max_length=64, default=ActionChoices.reject, verbose_name=_('Action')) reviewers = models.ManyToManyField('users.User', blank=True, verbose_name=_("Reviewers")) is_active = models.BooleanField(default=True, verbose_name=_("Active")) - comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) ActionChoices = ActionChoices objects = ACLManager.from_queryset(BaseACLQuerySet)() diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 3db5a738a..b02bc09be 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -7,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty, get_logger from orgs.mixins.models import JMSOrgBaseModel - from .base import UserAssetAccountBaseACL logger = get_logger(__file__) @@ -26,7 +25,6 @@ class CommandGroup(JMSOrgBaseModel): ) content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case')) - comment = models.TextField(blank=True, verbose_name=_("Comment")) TypeChoices = TypeChoices diff --git a/apps/applications/migrations/0010_appaccount_historicalappaccount.py b/apps/applications/migrations/0010_appaccount_historicalappaccount.py index cd0bf88d1..515754f32 100644 --- a/apps/applications/migrations/0010_appaccount_historicalappaccount.py +++ b/apps/applications/migrations/0010_appaccount_historicalappaccount.py @@ -1,17 +1,17 @@ # Generated by Django 3.1.12 on 2021-08-26 09:07 -import assets.models.base -import common.db.fields -from django.conf import settings +import uuid + import django.core.validators -from django.db import migrations, models import django.db.models.deletion import simple_history.models -import uuid +from django.conf import settings +from django.db import migrations, models + +import common.db.fields class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('assets', '0076_delete_assetuser'), @@ -22,14 +22,19 @@ class Migration(migrations.Migration): migrations.CreateModel( name='HistoricalAccount', fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('name', models.CharField(max_length=128, verbose_name='Name')), - ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), - ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[ + django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], + verbose_name='Username')), + ('password', + common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', + common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), @@ -37,10 +42,17 @@ class Migration(migrations.Migration): ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField()), ('history_change_reason', models.CharField(max_length=100, null=True)), - ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('app', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='applications.application', verbose_name='Database')), - ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), - ('systemuser', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.systemuser', verbose_name='System user')), + ('history_type', + models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('app', models.ForeignKey(blank=True, db_constraint=False, null=True, + on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', + to='applications.application', verbose_name='Database')), + ('history_user', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', + to=settings.AUTH_USER_MODEL)), + ('systemuser', models.ForeignKey(blank=True, db_constraint=False, null=True, + on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', + to='assets.systemuser', verbose_name='System user')), ], options={ 'verbose_name': 'historical Account', @@ -52,20 +64,28 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Account', fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), - ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), - ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[ + django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], + verbose_name='Username')), + ('password', + common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', + common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('version', models.IntegerField(default=1, verbose_name='Version')), - ('app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='applications.application', verbose_name='Database')), - ('systemuser', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser', verbose_name='System user')), + ('app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, + to='applications.application', verbose_name='Database')), + ('systemuser', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser', + verbose_name='System user')), ], options={ 'verbose_name': 'Account', diff --git a/apps/applications/migrations/0027_auto_20221220_1956.py b/apps/applications/migrations/0027_auto_20221220_1956.py new file mode 100644 index 000000000..a72162974 --- /dev/null +++ b/apps/applications/migrations/0027_auto_20221220_1956.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0026_auto_20220817_1716'), + ] + + operations = [ + migrations.AddField( + model_name='application', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='application', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='application', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + ] diff --git a/apps/applications/models.py b/apps/applications/models.py index f1b1bbc8a..60efc8aab 100644 --- a/apps/applications/models.py +++ b/apps/applications/models.py @@ -1,12 +1,11 @@ - from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.db.models import JMSBaseModel from orgs.mixins.models import OrgModelMixin -from common.mixins import CommonModelMixin -class Application(CommonModelMixin, OrgModelMixin): +class Application(JMSBaseModel, OrgModelMixin): name = models.CharField(max_length=128, verbose_name=_('Name')) category = models.CharField( max_length=16, verbose_name=_('Category') @@ -15,9 +14,6 @@ class Application(CommonModelMixin, OrgModelMixin): max_length=16, verbose_name=_('Type') ) attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) - comment = models.TextField( - max_length=128, default='', blank=True, verbose_name=_('Comment') - ) class Meta: verbose_name = _('Application') diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index 01ad1cf1f..58f917ac4 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -1,15 +1,16 @@ # Generated by Django 3.2.12 on 2022-07-11 08:59 -import common.db.fields -from django.conf import settings -from django.db import migrations, models +import uuid + import django.db.models.deletion import simple_history.models -import uuid +from django.conf import settings +from django.db import migrations, models + +import common.db.fields class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('assets', '0098_auto_20220430_2126'), @@ -20,14 +21,19 @@ class Migration(migrations.Migration): name='HistoricalAccount', fields=[ ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), - ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('secret_type', models.CharField( + choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), + ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('version', models.IntegerField(default=0, verbose_name='Version'),), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), ('history_change_reason', models.CharField(max_length=100, null=True)), - ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('history_type', + models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', + to=settings.AUTH_USER_MODEL)), ], options={ 'verbose_name': 'historical Account', @@ -40,43 +46,55 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Account', fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), - ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('secret_type', models.CharField( + choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), + ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), - ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], + default='unknown', max_length=16, verbose_name='Connectivity')), ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('version', models.IntegerField(default=0, verbose_name='Version')), - ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', + to='assets.asset', verbose_name='Asset')), ], options={ 'verbose_name': 'Account', - 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], + 'permissions': [('view_accountsecret', 'Can view asset account secret'), + ('change_accountsecret', 'Can change asset account secret'), + ('view_historyaccount', 'Can view asset history account'), + ('view_historyaccountsecret', 'Can view asset history account secret')], 'unique_together': {('name', 'asset'), ('username', 'asset', 'secret_type')}, }, ), migrations.AddField( model_name='account', name='su_from', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', + to='assets.account', verbose_name='Su from'), ), migrations.CreateModel( name='AccountTemplate', fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), - ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),), + ('secret_type', models.CharField( + choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), + ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), diff --git a/apps/assets/migrations/0115_auto_20221220_1956.py b/apps/assets/migrations/0115_auto_20221220_1956.py new file mode 100644 index 000000000..976a8c53b --- /dev/null +++ b/apps/assets/migrations/0115_auto_20221220_1956.py @@ -0,0 +1,183 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0114_remove_redundant_macos'), + ] + + operations = [ + migrations.AddField( + model_name='accountbackupplan', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='baseautomation', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='changesecretrecord', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='domain', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AddField( + model_name='domain', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='domain', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='favoriteasset', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='favoriteasset', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='gathereduser', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='gathereduser', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AddField( + model_name='gathereduser', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='node', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='node', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AddField( + model_name='node', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + migrations.AddField( + model_name='node', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='node', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='account', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='account', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='accountbackupplan', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='accountbackupplan', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='accounttemplate', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='accounttemplate', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='asset', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='asset', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='baseautomation', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='baseautomation', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='changesecretrecord', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='changesecretrecord', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='domain', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='favoriteasset', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='gathereduser', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + migrations.AlterField( + model_name='label', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='label', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='label', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index e7d3ae8db..72d494948 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -2,18 +2,17 @@ # -*- coding: utf-8 -*- # -import uuid import logging +import uuid from collections import defaultdict from django.db import models -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty from orgs.mixins.models import OrgManager, JMSOrgBaseModel -from ..platform import Platform from ..base import AbsConnectivity +from ..platform import Platform __all__ = ['Asset', 'AssetQuerySet', 'default_node', 'Protocol'] logger = logging.getLogger(__name__) @@ -110,7 +109,6 @@ 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")) - comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) info = models.JSONField(verbose_name='Info', default=dict, blank=True) objects = AssetManager.from_queryset(AssetQuerySet)() diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 9977f6830..a09084fe5 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -1,25 +1,24 @@ import uuid + from celery import current_task from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.const.choices import Trigger -from common.mixins.models import CommonModelMixin -from common.db.fields import EncryptJsonDictTextField -from orgs.mixins.models import OrgModelMixin -from ops.mixin import PeriodTaskModelMixin +from assets.const import AutomationTypes from assets.models import Node, Asset from assets.tasks import execute_automation -from assets.const import AutomationTypes +from common.const.choices import Trigger +from common.db.fields import EncryptJsonDictTextField +from ops.mixin import PeriodTaskModelMixin +from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel -class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): +class BaseAutomation(PeriodTaskModelMixin, JMSOrgBaseModel): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type')) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) - comment = models.TextField(blank=True, verbose_name=_('Comment')) def __str__(self): return self.name + '@' + str(self.created_by) diff --git a/apps/assets/models/backup.py b/apps/assets/models/backup.py index 3cf49a94d..d4a8b8cc8 100644 --- a/apps/assets/models/backup.py +++ b/apps/assets/models/backup.py @@ -7,26 +7,23 @@ from celery import current_task from django.db import models from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgModelMixin -from ops.mixin import PeriodTaskModelMixin -from common.utils import get_logger from common.const.choices import Trigger from common.db.encoder import ModelJSONFieldEncoder -from common.mixins.models import CommonModelMixin +from common.utils import get_logger +from ops.mixin import PeriodTaskModelMixin +from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel __all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution'] logger = get_logger(__file__) -class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel): types = models.JSONField(default=list) recipients = models.ManyToManyField( 'users.User', related_name='recipient_escape_route_plans', blank=True, verbose_name=_("Recipient") ) - comment = models.TextField(blank=True, verbose_name=_('Comment')) def __str__(self): return f'{self.name}({self.org_id})' diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 03697d427..8c8c08555 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -69,8 +69,6 @@ class BaseAccount(JMSOrgBaseModel): secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) - comment = models.TextField(blank=True, verbose_name=_('Comment')) - created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) objects = BaseAccountManager.from_queryset(BaseAccountQuerySet)() diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 9c70f2bb9..dd0d7b0d1 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -7,11 +7,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger -from users.models import User, UserGroup -from applications.models import Application -from ..models import SystemUser, Asset, Node - -from common.utils import lazyproperty, get_logger, get_object_or_none from orgs.mixins.models import OrgModelMixin logger = get_logger(__file__) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 77758283d..4e2cfc71d 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- # -import uuid import random from django.db import models from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty -from orgs.mixins.models import OrgModelMixin - +from orgs.mixins.models import JMSOrgBaseModel from .gateway import Gateway logger = get_logger(__file__) @@ -16,11 +14,8 @@ logger = get_logger(__file__) __all__ = ['Domain'] -class Domain(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class Domain(JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_('Name')) - comment = models.TextField(blank=True, verbose_name=_('Comment')) - date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) class Meta: verbose_name = _("Domain") @@ -51,5 +46,3 @@ class Domain(OrgModelMixin): @classmethod def get_gateway_queryset(cls): return Gateway.objects.all() - - diff --git a/apps/assets/models/favorite_asset.py b/apps/assets/models/favorite_asset.py index c5e6db484..8fbaeed64 100644 --- a/apps/assets/models/favorite_asset.py +++ b/apps/assets/models/favorite_asset.py @@ -2,13 +2,12 @@ # from django.db import models -from common.mixins.models import CommonModelMixin - +from common.db.models import JMSBaseModel __all__ = ['FavoriteAsset'] -class FavoriteAsset(CommonModelMixin): +class FavoriteAsset(JMSBaseModel): user = models.ForeignKey('users.User', on_delete=models.CASCADE) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE) diff --git a/apps/assets/models/gathered_user.py b/apps/assets/models/gathered_user.py index 3c0a743b9..4f7ae4bb8 100644 --- a/apps/assets/models/gathered_user.py +++ b/apps/assets/models/gathered_user.py @@ -1,23 +1,19 @@ # -*- coding: utf-8 -*- # -import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgModelMixin +from orgs.mixins.models import JMSOrgBaseModel __all__ = ['GatheredUser'] -class GatheredUser(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class GatheredUser(JMSOrgBaseModel): asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset")) username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username')) present = models.BooleanField(default=True, verbose_name=_("Present")) date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login")) ip_last_login = models.CharField(max_length=39, default='', verbose_name=_("IP last login")) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) - date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) @property def name(self): @@ -33,6 +29,3 @@ class GatheredUser(OrgModelMixin): def __str__(self): return '{}: {}'.format(self.asset.name, self.username) - - - diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index 2e0ff92ee..3aff63385 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -20,7 +20,6 @@ class Label(JMSOrgBaseModel): category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) - comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) @classmethod def get_queryset_group_by_name(cls): diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 960d858d5..5d1af0ec5 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -1,29 +1,24 @@ # -*- coding: utf-8 -*- # import re -import time -import uuid import threading -import os import time import uuid - from collections import defaultdict + +from django.core.cache import cache from django.db import models, transaction from django.db.models import Q, Manager -from django.db.utils import IntegrityError -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ugettext from django.db.transaction import atomic -from django.core.cache import cache +from django.utils.translation import ugettext +from django.utils.translation import ugettext_lazy as _ -from common.utils.lock import DistributedLock -from common.utils.common import timeit from common.db.models import output_as_string from common.utils import get_logger -from orgs.mixins.models import OrgModelMixin, OrgManager -from orgs.utils import get_current_org, tmp_to_org, tmp_to_root_org +from common.utils.lock import DistributedLock +from orgs.mixins.models import OrgManager, JMSOrgBaseModel from orgs.models import Organization +from orgs.utils import get_current_org, tmp_to_org, tmp_to_root_org __all__ = ['Node', 'FamilyMixin', 'compute_parent_key', 'NodeQuerySet'] logger = get_logger(__name__) @@ -545,7 +540,7 @@ class SomeNodesMixin: return root_nodes -class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin): +class Node(JMSOrgBaseModel, SomeNodesMixin, FamilyMixin, NodeAssetsMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1' value = models.CharField(max_length=128, verbose_name=_("Value")) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index fd803184c..f5aee2f65 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -19,9 +19,9 @@ class JobAuditLogSerializer(JobExecutionSerializer): class Meta: model = JobAuditLog read_only_fields = [ - "id", "material", "time_cost", - 'date_start', 'date_finished', 'date_created', - 'is_finished', 'is_success', 'creator_by' + "id", "material", "time_cost", 'date_start', + 'date_finished', 'date_created', + 'is_finished', 'is_success', 'created_by', ] fields = read_only_fields + [] diff --git a/apps/authentication/migrations/0016_auto_20221220_1956.py b/apps/authentication/migrations/0016_auto_20221220_1956.py new file mode 100644 index 000000000..d1480629c --- /dev/null +++ b/apps/authentication/migrations/0016_auto_20221220_1956.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0015_auto_20221205_1136'), + ] + + operations = [ + migrations.AddField( + model_name='connectiontoken', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='ssotoken', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='temptoken', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='ssotoken', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='ssotoken', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='temptoken', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='temptoken', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 421ec0969..bb7d282b1 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -11,10 +11,9 @@ from rest_framework.exceptions import PermissionDenied from assets.const import Protocol from common.db.fields import EncryptCharField -from common.db.models import JMSBaseModel from common.utils import lazyproperty, pretty_string, bulk_get from common.utils.timezone import as_current_tz -from orgs.mixins.models import OrgModelMixin +from orgs.mixins.models import JMSOrgBaseModel from terminal.models import Applet @@ -22,7 +21,7 @@ def date_expired_default(): return timezone.now() + timedelta(seconds=settings.CONNECTION_TOKEN_EXPIRATION) -class ConnectionToken(OrgModelMixin, JMSBaseModel): +class ConnectionToken(JMSOrgBaseModel): value = models.CharField(max_length=64, default='', verbose_name=_("Value")) user = models.ForeignKey( 'users.User', on_delete=models.SET_NULL, null=True, blank=True, diff --git a/apps/common/db/models.py b/apps/common/db/models.py index 10f553410..7be495538 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -72,10 +72,11 @@ class ChoicesMixin: class BaseCreateUpdateModel(models.Model): - created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) - updated_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Updated by')) + created_by = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Created by')) + updated_by = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Updated by')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) class Meta: abstract = True diff --git a/apps/common/mixins/models.py b/apps/common/mixins/models.py index e0d9b392a..f6705f53c 100644 --- a/apps/common/mixins/models.py +++ b/apps/common/mixins/models.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- # -import uuid + from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ __all__ = [ "NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet", - "CommonModelMixin" ] @@ -44,16 +43,6 @@ class NoDeleteModelMixin(models.Model): return self.save() -class CommonModelMixin(models.Model): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) - date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) - - class Meta: - abstract = True - - class DebugQueryManager(models.Manager): def get_queryset(self): import traceback @@ -64,5 +53,3 @@ class DebugQueryManager(models.Manager): print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") queryset = super().get_queryset() return queryset - - diff --git a/apps/notifications/migrations/0003_auto_20221220_1956.py b/apps/notifications/migrations/0003_auto_20221220_1956.py new file mode 100644 index 000000000..7cbac3e18 --- /dev/null +++ b/apps/notifications/migrations/0003_auto_20221220_1956.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0002_auto_20210909_1946'), + ] + + operations = [ + migrations.AlterField( + model_name='sitemessage', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='sitemessage', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='sitemessageusers', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='sitemessageusers', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='systemmsgsubscription', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='systemmsgsubscription', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='usermsgsubscription', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='usermsgsubscription', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + ] diff --git a/apps/notifications/models/notification.py b/apps/notifications/models/notification.py index 24995e975..410d07a28 100644 --- a/apps/notifications/models/notification.py +++ b/apps/notifications/models/notification.py @@ -12,6 +12,7 @@ class UserMsgSubscription(JMSBaseModel): verbose_name=_('User') ) receive_backends = models.JSONField(default=list, verbose_name=_('receive backend')) + comment = '' class Meta: verbose_name = _('User message') @@ -25,6 +26,7 @@ class SystemMsgSubscription(JMSBaseModel): users = models.ManyToManyField('users.User', related_name='system_msg_subscriptions') groups = models.ManyToManyField('users.UserGroup', related_name='system_msg_subscriptions') receive_backends = models.JSONField(default=list) + comment = '' message_type_label = '' diff --git a/apps/notifications/models/site_msg.py b/apps/notifications/models/site_msg.py index e08cd5c71..170f21a3f 100644 --- a/apps/notifications/models/site_msg.py +++ b/apps/notifications/models/site_msg.py @@ -6,10 +6,13 @@ __all__ = ('SiteMessageUsers', 'SiteMessage') class SiteMessageUsers(JMSBaseModel): - sitemessage = models.ForeignKey('notifications.SiteMessage', on_delete=models.CASCADE, db_constraint=False, related_name='m2m_sitemessageusers') - user = models.ForeignKey('users.User', on_delete=models.CASCADE, db_constraint=False, related_name='m2m_sitemessageusers') + sitemessage = models.ForeignKey('notifications.SiteMessage', on_delete=models.CASCADE, db_constraint=False, + related_name='m2m_sitemessageusers') + user = models.ForeignKey('users.User', on_delete=models.CASCADE, db_constraint=False, + related_name='m2m_sitemessageusers') has_read = models.BooleanField(default=False) read_at = models.DateTimeField(default=None, null=True) + comment = '' class SiteMessage(JMSBaseModel): @@ -24,6 +27,7 @@ class SiteMessage(JMSBaseModel): 'users.User', db_constraint=False, on_delete=models.DO_NOTHING, null=True, default=None, related_name='send_site_message' ) + comment = '' has_read = False read_at = None diff --git a/apps/ops/migrations/0026_auto_20221009_2050.py b/apps/ops/migrations/0026_auto_20221009_2050.py index 699246531..b9965c2bd 100644 --- a/apps/ops/migrations/0026_auto_20221009_2050.py +++ b/apps/ops/migrations/0026_auto_20221009_2050.py @@ -1,13 +1,13 @@ # Generated by Django 3.2.14 on 2022-10-09 12:50 +import uuid + +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion -import uuid class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('assets', '0106_auto_20220916_1556'), @@ -23,7 +23,8 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('name', models.CharField(max_length=128, verbose_name='Name')), ('is_periodic', models.BooleanField(default=False)), ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), @@ -32,7 +33,7 @@ class Migration(migrations.Migration): ('account_policy', models.CharField(default='root', max_length=128, verbose_name='Account policy')), ('date_last_run', models.DateTimeField(null=True, verbose_name='Date last run')), ('path', models.FilePathField(max_length=1024, verbose_name='Playbook')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')), ], options={ @@ -52,10 +53,11 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('name', models.CharField(max_length=128, verbose_name='Name')), ('path', models.FilePathField(verbose_name='Path')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ], options={ 'verbose_name': 'Playbook template', @@ -74,8 +76,11 @@ class Migration(migrations.Migration): ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), ('path', models.FilePathField(max_length=1024, verbose_name='Run dir')), - ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ops.playbook', verbose_name='Task')), + ('creator', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, + verbose_name='Creator')), + ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ops.playbook', + verbose_name='Task')), ], options={ 'ordering': ['-date_start'], @@ -85,16 +90,19 @@ class Migration(migrations.Migration): migrations.AddField( model_name='playbook', name='last_execution', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbookexecution', verbose_name='Last execution'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + to='ops.playbookexecution', verbose_name='Last execution'), ), migrations.AddField( model_name='playbook', name='owner', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Owner'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, verbose_name='Owner'), ), migrations.AddField( model_name='playbook', name='template', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbooktemplate', verbose_name='Template'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbooktemplate', + verbose_name='Template'), ), ] diff --git a/apps/ops/migrations/0031_auto_20221220_1956.py b/apps/ops/migrations/0031_auto_20221220_1956.py new file mode 100644 index 000000000..616d6f4f3 --- /dev/null +++ b/apps/ops/migrations/0031_auto_20221220_1956.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0030_jobauditlog'), + ] + + operations = [ + migrations.AddField( + model_name='jobexecution', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='adhoc', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='adhoc', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='job', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='job', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='jobexecution', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='jobexecution', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='playbook', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='playbook', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + ] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 4f003895e..b849d6a76 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -1,13 +1,13 @@ import json +import logging import os import uuid -import logging +from celery import current_task from django.conf import settings from django.db import models -from django.utils.translation import gettext_lazy as _ from django.utils import timezone -from celery import current_task +from django.utils.translation import gettext_lazy as _ __all__ = ["Job", "JobExecution", "JobAuditLog"] @@ -19,7 +19,6 @@ from orgs.mixins.models import JMSOrgBaseModel class Job(JMSOrgBaseModel, PeriodTaskModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) 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) diff --git a/apps/orgs/migrations/0001_initial.py b/apps/orgs/migrations/0001_initial.py index 7241ce6a2..a4705e719 100644 --- a/apps/orgs/migrations/0001_initial.py +++ b/apps/orgs/migrations/0001_initial.py @@ -1,12 +1,12 @@ # Generated by Django 2.0.7 on 2018-08-07 03:16 +import uuid + from django.conf import settings from django.db import migrations, models -import uuid class Migration(migrations.Migration): - initial = True dependencies = [ @@ -21,7 +21,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('admins', models.ManyToManyField(blank=True, related_name='admin_orgs', to=settings.AUTH_USER_MODEL)), ('users', models.ManyToManyField(blank=True, related_name='orgs', to=settings.AUTH_USER_MODEL)), ], diff --git a/apps/orgs/migrations/0015_auto_20221220_1956.py b/apps/orgs/migrations/0015_auto_20221220_1956.py new file mode 100644 index 000000000..df92f6862 --- /dev/null +++ b/apps/orgs/migrations/0015_auto_20221220_1956.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orgs', '0014_organization_builtin'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='organization', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='organization', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + ] diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 89f50f4bb..be88ec242 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -1,8 +1,7 @@ -import uuid - from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.db.models import JMSBaseModel from common.tree import TreeNode from common.utils import lazyproperty, settings @@ -64,13 +63,9 @@ class OrgRoleMixin: return self.get_origin_role_members('user') -class Organization(OrgRoleMixin, models.Model): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class Organization(OrgRoleMixin, JMSBaseModel): name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) - created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) builtin = models.BooleanField(default=False, verbose_name=_('Builtin')) - date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) - comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) members = models.ManyToManyField( 'users.User', related_name='orgs', through='rbac.RoleBinding', through_fields=('org', 'user') ) diff --git a/apps/perms/migrations/0034_auto_20221220_1956.py b/apps/perms/migrations/0034_auto_20221220_1956.py new file mode 100644 index 000000000..01b7f0f5f --- /dev/null +++ b/apps/perms/migrations/0034_auto_20221220_1956.py @@ -0,0 +1,54 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0033_alter_assetpermission_actions'), + ] + + operations = [ + migrations.AddField( + model_name='assetpermission', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='assetpermission', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='assetpermission', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='assetpermission', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='assetpermission', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + migrations.AlterField( + model_name='userassetgrantedtreenoderelation', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='userassetgrantedtreenoderelation', + name='id', + field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='userassetgrantedtreenoderelation', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + ] diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py index 9041990f2..63bd0b025 100644 --- a/apps/perms/models/__init__.py +++ b/apps/perms/models/__init__.py @@ -3,4 +3,3 @@ from .asset_permission import * from .perm_node import * -from .perm_token import * diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index e3b2afdca..ac6fecdb4 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -1,4 +1,3 @@ -import uuid import logging from django.db import models @@ -6,16 +5,14 @@ from django.db.models import Q from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from users.models import User from assets.models import Asset, Account -from orgs.mixins.models import OrgManager -from orgs.mixins.models import OrgModelMixin -from common.utils.timezone import local_now from common.db.models import UnionQuerySet from common.utils import date_expired_default - +from common.utils.timezone import local_now +from orgs.mixins.models import JMSOrgBaseModel +from orgs.mixins.models import OrgManager from perms.const import ActionChoices -from .perm_node import PermNode +from users.models import User __all__ = ['AssetPermission', 'ActionChoices'] @@ -54,8 +51,7 @@ class AssetPermissionManager(OrgManager): return self.get_queryset().filter(Q(date_start__lte=now) | Q(date_expired__gte=now)) -class AssetPermission(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class AssetPermission(JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_('Name')) users = models.ManyToManyField( 'users.User', related_name='%(class)ss', blank=True, verbose_name=_("User") @@ -76,11 +72,8 @@ class AssetPermission(OrgModelMixin): date_expired = models.DateTimeField( default=date_expired_default, db_index=True, verbose_name=_('Date expired') ) - comment = models.TextField(verbose_name=_('Comment'), blank=True) is_active = models.BooleanField(default=True, verbose_name=_('Active')) from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) - created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)() @@ -142,11 +135,11 @@ class AssetPermission(OrgModelMixin): @classmethod def get_all_users_for_perms(cls, perm_ids, flat=False): - user_ids = cls.users.through.objects.filter(assetpermission_id__in=perm_ids)\ + user_ids = cls.users.through.objects.filter(assetpermission_id__in=perm_ids) \ .values_list('user_id', flat=True).distinct() - group_ids = cls.user_groups.through.objects.filter(assetpermission_id__in=perm_ids)\ + group_ids = cls.user_groups.through.objects.filter(assetpermission_id__in=perm_ids) \ .values_list('usergroup_id', flat=True).distinct() - group_user_ids = User.groups.through.objects.filter(usergroup_id__in=group_ids)\ + group_user_ids = User.groups.through.objects.filter(usergroup_id__in=group_ids) \ .values_list('user_id', flat=True).distinct() user_ids = set(user_ids) | set(group_user_ids) if flat: diff --git a/apps/perms/models/perm_node.py b/apps/perms/models/perm_node.py index 4dddae98f..675e36e9c 100644 --- a/apps/perms/models/perm_node.py +++ b/apps/perms/models/perm_node.py @@ -1,12 +1,10 @@ - -from django.utils.translation import ugettext_lazy as _ from django.db import models from django.db.models import F, TextChoices +from django.utils.translation import ugettext_lazy as _ from assets.models import Asset, Node, FamilyMixin, Account -from orgs.mixins.models import OrgModelMixin from common.utils import lazyproperty -from common.db.models import BaseCreateUpdateModel +from orgs.mixins.models import JMSOrgBaseModel class NodeFrom(TextChoices): @@ -15,7 +13,7 @@ class NodeFrom(TextChoices): asset = 'asset', 'Direct asset granted' -class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): +class UserAssetGrantedTreeNodeRelation(FamilyMixin, JMSOrgBaseModel): NodeFrom = NodeFrom user = models.ForeignKey('users.User', db_constraint=False, on_delete=models.CASCADE) @@ -26,6 +24,7 @@ class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpd db_index=True) node_from = models.CharField(choices=NodeFrom.choices, max_length=16, db_index=True) node_assets_amount = models.IntegerField(default=0) + comment = '' @property def key(self): diff --git a/apps/perms/models/perm_token.py b/apps/perms/models/perm_token.py deleted file mode 100644 index 368750c63..000000000 --- a/apps/perms/models/perm_token.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - - -class PermToken(models.Model): - """ - 1. 用完失效 - 2. 仅用于授权,不用于认证 - 3. 存 redis 就行 - 4. 有效期 5 分钟 - """ - user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User')) - asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) - account = models.CharField(max_length=128, verbose_name=_('Account')) - secret = models.CharField(max_length=1024, verbose_name=_('Secret')) - protocol = models.CharField(max_length=32, verbose_name=_('Protocol')) - connect_method = models.CharField(max_length=32, verbose_name=_('Connect method')) - actions = models.IntegerField(verbose_name=_('Actions')) - - class Meta: - abstract = True diff --git a/apps/rbac/migrations/0010_auto_20221220_1956.py b/apps/rbac/migrations/0010_auto_20221220_1956.py new file mode 100644 index 000000000..0dee17c55 --- /dev/null +++ b/apps/rbac/migrations/0010_auto_20221220_1956.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rbac', '0009_auto_20220411_1724'), + ] + + operations = [ + migrations.AddField( + model_name='rolebinding', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='role', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='role', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='rolebinding', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='rolebinding', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + ] diff --git a/apps/terminal/migrations/0016_commandstorage_replaystorage.py b/apps/terminal/migrations/0016_commandstorage_replaystorage.py index 1f1b04966..5eb17046a 100644 --- a/apps/terminal/migrations/0016_commandstorage_replaystorage.py +++ b/apps/terminal/migrations/0016_commandstorage_replaystorage.py @@ -24,7 +24,7 @@ class Migration(migrations.Migration): ('type', models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('es', 'Elasticsearch')], default='server', max_length=16, verbose_name='Type')), ('meta', common.db.fields.EncryptJsonDictTextField(default={})), - ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ], options={ 'abstract': False, @@ -43,7 +43,7 @@ class Migration(migrations.Migration): ('oss', 'OSS'), ('azure', 'Azure')], default='server', max_length=16, verbose_name='Type')), ('meta', common.db.fields.EncryptJsonDictTextField(default={})), - ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ], options={ 'abstract': False, diff --git a/apps/terminal/migrations/0064_auto_20221220_1956.py b/apps/terminal/migrations/0064_auto_20221220_1956.py new file mode 100644 index 000000000..93b47dedc --- /dev/null +++ b/apps/terminal/migrations/0064_auto_20221220_1956.py @@ -0,0 +1,178 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0063_applet_builtin'), + ] + + operations = [ + migrations.AddField( + model_name='commandstorage', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='replaystorage', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='sessionjoinrecord', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='sessionjoinrecord', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='sessionreplay', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='sessionreplay', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='sessionsharing', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='sessionsharing', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='task', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='task', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AddField( + model_name='task', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='task', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='terminal', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AddField( + model_name='terminal', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='terminal', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='applet', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='applet', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='applethostdeployment', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='applethostdeployment', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='appletpublication', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='appletpublication', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='commandstorage', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='endpoint', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='endpoint', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='endpointrule', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='endpointrule', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='replaystorage', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='sessionjoinrecord', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='sessionreplay', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='sessionsharing', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='task', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + migrations.AlterField( + model_name='terminal', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='terminal', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + ] diff --git a/apps/terminal/models/component/storage.py b/apps/terminal/models/component/storage.py index b6878fb64..a3f8e4b54 100644 --- a/apps/terminal/models/component/storage.py +++ b/apps/terminal/models/component/storage.py @@ -1,22 +1,23 @@ from __future__ import unicode_literals + import copy import os from importlib import import_module import jms_storage +from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.conf import settings -from common.mixins import CommonModelMixin +from common.db.fields import EncryptJsonDictTextField +from common.db.models import JMSBaseModel from common.plugins.es import QuerySet as ESQuerySet from common.utils import get_logger -from common.db.fields import EncryptJsonDictTextField from common.utils.timezone import local_now_date_display +from terminal import const from terminal.backends import TYPE_ENGINE_MAPPING from .terminal import Terminal from ..session.command import Command -from terminal import const logger = get_logger(__file__) @@ -25,7 +26,6 @@ class CommonStorageModelMixin(models.Model): name = models.CharField(max_length=128, verbose_name=_("Name"), unique=True) meta = EncryptJsonDictTextField(default={}) is_default = models.BooleanField(default=False, verbose_name=_('Default storage')) - comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) class Meta: abstract = True @@ -51,7 +51,7 @@ class CommonStorageModelMixin(models.Model): return objs.first() -class CommandStorage(CommonStorageModelMixin, CommonModelMixin): +class CommandStorage(CommonStorageModelMixin, JMSBaseModel): type = models.CharField( max_length=16, choices=const.CommandStorageType.choices, default=const.CommandStorageType.server.value, verbose_name=_('Type'), @@ -140,7 +140,7 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): verbose_name = _("Command storage") -class ReplayStorage(CommonStorageModelMixin, CommonModelMixin): +class ReplayStorage(CommonStorageModelMixin, JMSBaseModel): type = models.CharField( max_length=16, choices=const.ReplayStorageType.choices, default=const.ReplayStorageType.server.value, verbose_name=_('Type') diff --git a/apps/terminal/models/component/task.py b/apps/terminal/models/component/task.py index 0225dc641..1c081da53 100644 --- a/apps/terminal/models/component/task.py +++ b/apps/terminal/models/component/task.py @@ -1,27 +1,24 @@ from __future__ import unicode_literals -import uuid - from django.db import models from django.utils.translation import ugettext_lazy as _ + +from common.db.models import JMSBaseModel from .terminal import Terminal -class Task(models.Model): +class Task(JMSBaseModel): NAME_CHOICES = ( ("kill_session", "Kill Session"), ) - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, choices=NAME_CHOICES, verbose_name=_("Name")) args = models.CharField(max_length=1024, verbose_name=_("Args")) kwargs = models.JSONField(default=dict, verbose_name=_("Kwargs")) terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL) is_finished = models.BooleanField(default=False) - date_created = models.DateTimeField(auto_now_add=True) date_finished = models.DateTimeField(null=True) class Meta: db_table = "terminal_task" verbose_name = _("Task") - diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index e244c8a40..406d2192a 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,11 +1,11 @@ import time -import uuid from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ from common.const.signals import SKIP_SIGNAL +from common.db.models import JMSBaseModel from common.utils import get_logger, lazyproperty from orgs.utils import tmp_to_root_org from terminal.const import TerminalType as TypeChoices @@ -75,8 +75,7 @@ class StorageMixin: return {"TERMINAL_REPLAY_STORAGE": config} -class Terminal(StorageMixin, TerminalStatusMixin, models.Model): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class Terminal(StorageMixin, TerminalStatusMixin, JMSBaseModel): name = models.CharField(max_length=128, verbose_name=_('Name')) type = models.CharField( choices=TypeChoices.choices, default=TypeChoices.koko, @@ -88,8 +87,6 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): user = models.OneToOneField(User, related_name='terminal', verbose_name=_('Application User'), null=True, on_delete=models.CASCADE) is_deleted = models.BooleanField(default=False) - date_created = models.DateTimeField(auto_now_add=True) - comment = models.TextField(blank=True, verbose_name=_('Comment')) @property def is_active(self): diff --git a/apps/terminal/models/session/replay.py b/apps/terminal/models/session/replay.py index 5f3e372af..9fd210f48 100644 --- a/apps/terminal/models/session/replay.py +++ b/apps/terminal/models/session/replay.py @@ -1,11 +1,11 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.mixins.models import CommonModelMixin +from common.db.models import JMSBaseModel from .session import Session -class SessionReplay(CommonModelMixin): +class SessionReplay(JMSBaseModel): session = models.ForeignKey(Session, on_delete=models.CASCADE, verbose_name=_("Session")) class Meta: @@ -14,5 +14,3 @@ class SessionReplay(CommonModelMixin): ('upload_sessionreplay', _("Can upload session replay")), ('download_sessionreplay', _("Can download session replay")), ] - - diff --git a/apps/terminal/models/session/sharing.py b/apps/terminal/models/session/sharing.py index 8675ced01..b71cad900 100644 --- a/apps/terminal/models/session/sharing.py +++ b/apps/terminal/models/session/sharing.py @@ -1,20 +1,19 @@ import datetime from django.db import models -from django.utils.translation import ugettext_lazy as _ from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + +from common.db.models import JMSBaseModel +from orgs.mixins.models import OrgModelMixin from orgs.utils import tmp_to_root_org from users.models import User - -from common.mixins import CommonModelMixin -from orgs.mixins.models import OrgModelMixin from .session import Session - __all__ = ['SessionSharing', 'SessionJoinRecord'] -class SessionSharing(CommonModelMixin, OrgModelMixin): +class SessionSharing(JMSBaseModel, OrgModelMixin): session = models.ForeignKey( 'terminal.Session', on_delete=models.CASCADE, verbose_name=_('Session') ) @@ -33,7 +32,7 @@ class SessionSharing(CommonModelMixin, OrgModelMixin): users = models.TextField(blank=True, verbose_name=_("User")) class Meta: - ordering = ('-date_created', ) + ordering = ('-date_created',) verbose_name = _('Session sharing') permissions = [ ('add_supersessionsharing', _("Can add super session sharing")) @@ -71,7 +70,7 @@ class SessionSharing(CommonModelMixin, OrgModelMixin): return True, '' -class SessionJoinRecord(CommonModelMixin, OrgModelMixin): +class SessionJoinRecord(JMSBaseModel, OrgModelMixin): LOGIN_FROM = Session.LOGIN_FROM session = models.ForeignKey( @@ -112,7 +111,7 @@ class SessionJoinRecord(CommonModelMixin, OrgModelMixin): ) class Meta: - ordering = ('-date_joined', ) + ordering = ('-date_joined',) verbose_name = _("Session join record") def __str__(self): diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index 783021dc8..a79bf727a 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.14 on 2022-08-17 05:46 import time + from django.db import migrations, models @@ -15,7 +16,7 @@ def migrate_system_to_account(apps, schema_editor): (apply_login_asset_ticket_model, 'apply_login_system_user', 'apply_login_account', False), ) - print("\n\tStart migrate system user to account") + print("\n Start migrate system user to account") for model, old_field, new_field, m2m in model_system_user_account: print("\t - migrate '{}'".format(model.__name__)) count = 0 @@ -40,12 +41,11 @@ def migrate_system_to_account(apps, schema_editor): updated.append(obj) model.objects.bulk_update(updated, [new_field]) print(" Migrate account: {}-{} using: {:.2f}s".format( - count - len(objects), count, time.time()-start + count - len(objects), count, time.time() - start )) class Migration(migrations.Migration): - dependencies = [ ('tickets', '0019_delete_applyapplicationticket'), ] diff --git a/apps/tickets/migrations/0025_auto_20221206_1820.py b/apps/tickets/migrations/0025_auto_20221206_1820.py index c82a17ce8..ca9947158 100644 --- a/apps/tickets/migrations/0025_auto_20221206_1820.py +++ b/apps/tickets/migrations/0025_auto_20221206_1820.py @@ -1,13 +1,12 @@ # Generated by Django 3.2.14 on 2022-12-06 10:20 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('acls', '0010_auto_20221205_1122'), + ('acls', '0008_commandgroup_comment'), ('tickets', '0024_auto_20221121_1800'), ] @@ -23,6 +22,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='applycommandticket', name='apply_from_cmd_filter_acl', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='acls.commandfilteracl', verbose_name='Command filter acl'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='acls.commandfilteracl', + verbose_name='Command filter acl'), ), ] diff --git a/apps/tickets/migrations/0026_auto_20221220_1956.py b/apps/tickets/migrations/0026_auto_20221220_1956.py new file mode 100644 index 000000000..67f590f7b --- /dev/null +++ b/apps/tickets/migrations/0026_auto_20221220_1956.py @@ -0,0 +1,98 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0025_auto_20221206_1820'), + ] + + operations = [ + migrations.AddField( + model_name='approvalrule', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='approvalrule', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='comment', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='comment', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='ticket', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='ticketassignee', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='ticketassignee', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='ticketflow', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='ticketflow', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='ticketstep', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AddField( + model_name='ticketstep', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='approvalrule', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='comment', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='ticket', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='ticketassignee', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='ticketflow', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='ticketstep', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + ] diff --git a/apps/tickets/models/comment.py b/apps/tickets/models/comment.py index f15057d7e..638ecdc4d 100644 --- a/apps/tickets/models/comment.py +++ b/apps/tickets/models/comment.py @@ -3,12 +3,12 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.mixins.models import CommonModelMixin +from common.db.models import JMSBaseModel __all__ = ['Comment'] -class Comment(CommonModelMixin): +class Comment(JMSBaseModel): class Type(models.TextChoices): state = 'state', _('State') common = 'common', _('common') @@ -28,7 +28,7 @@ class Comment(CommonModelMixin): state = models.CharField(max_length=16, null=True) class Meta: - ordering = ('date_created', ) + ordering = ('date_created',) verbose_name = _("Comment") def set_display_fields(self): diff --git a/apps/tickets/models/flow.py b/apps/tickets/models/flow.py index 4dd468f57..0b7518fc6 100644 --- a/apps/tickets/models/flow.py +++ b/apps/tickets/models/flow.py @@ -3,18 +3,17 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from users.models import User -from common.mixins.models import CommonModelMixin - +from common.db.models import JMSBaseModel from orgs.mixins.models import OrgModelMixin from orgs.models import Organization from orgs.utils import tmp_to_org, get_current_org_id +from users.models import User from ..const import TicketType, TicketLevel, TicketApprovalStrategy __all__ = ['TicketFlow', 'ApprovalRule'] -class ApprovalRule(CommonModelMixin): +class ApprovalRule(JMSBaseModel): level = models.SmallIntegerField( default=TicketLevel.one, choices=TicketLevel.choices, verbose_name=_('Approve level') @@ -51,7 +50,7 @@ class ApprovalRule(CommonModelMixin): return assignees -class TicketFlow(CommonModelMixin, OrgModelMixin): +class TicketFlow(JMSBaseModel, OrgModelMixin): type = models.CharField( max_length=64, choices=TicketType.choices, default=TicketType.general, verbose_name=_("Type") diff --git a/apps/tickets/models/ticket/general.py b/apps/tickets/models/ticket/general.py index 271b87166..863a1508f 100644 --- a/apps/tickets/models/ticket/general.py +++ b/apps/tickets/models/ticket/general.py @@ -5,17 +5,17 @@ from typing import Callable from django.db import models from django.db.models import Q -from django.forms import model_to_dict -from django.db.utils import IntegrityError from django.db.models.fields import related +from django.db.utils import IntegrityError +from django.forms import model_to_dict from django.utils.translation import ugettext_lazy as _ -from orgs.utils import tmp_to_org -from orgs.models import Organization +from common.db.encoder import ModelJSONFieldEncoder +from common.db.models import JMSBaseModel from common.exceptions import JMSException from common.utils.timezone import as_current_tz -from common.mixins.models import CommonModelMixin -from common.db.encoder import ModelJSONFieldEncoder +from orgs.models import Organization +from orgs.utils import tmp_to_org from tickets.const import ( TicketType, TicketStatus, TicketState, TicketLevel, StepState, StepStatus @@ -25,11 +25,12 @@ from tickets.handlers import get_ticket_handler from ..flow import TicketFlow __all__ = [ - 'Ticket', 'TicketStep', 'TicketAssignee', 'SuperTicket', 'SubTicketManager' + 'Ticket', 'TicketStep', 'TicketAssignee', + 'SuperTicket', 'SubTicketManager' ] -class TicketStep(CommonModelMixin): +class TicketStep(JMSBaseModel): ticket = models.ForeignKey( 'Ticket', related_name='ticket_steps', on_delete=models.CASCADE, verbose_name='Ticket' @@ -74,7 +75,7 @@ class TicketStep(CommonModelMixin): verbose_name = _("Ticket step") -class TicketAssignee(CommonModelMixin): +class TicketAssignee(JMSBaseModel): assignee = models.ForeignKey( 'users.User', related_name='ticket_assignees', on_delete=models.CASCADE, verbose_name='Assignee' @@ -267,7 +268,7 @@ class StatusMixin: return get_ticket_handler(ticket=self) -class Ticket(StatusMixin, CommonModelMixin): +class Ticket(StatusMixin, JMSBaseModel): title = models.CharField(max_length=256, verbose_name=_('Title')) type = models.CharField( max_length=64, choices=TicketType.choices, diff --git a/apps/users/migrations/0001_initial.py b/apps/users/migrations/0001_initial.py index 01edf24b6..d5c584181 100644 --- a/apps/users/migrations/0001_initial.py +++ b/apps/users/migrations/0001_initial.py @@ -2,15 +2,16 @@ # Generated by Django 1.11 on 2017-12-21 16:06 from __future__ import unicode_literals -import common.utils -from django.contrib.auth.hashers import make_password -from django.conf import settings -import django.contrib.auth.models -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone import uuid +import django.contrib.auth.models +import django.utils.timezone +from django.conf import settings +from django.contrib.auth.hashers import make_password +from django.db import migrations, models + +import common.utils + def add_default_group(apps, schema_editor): group_model = apps.get_model("users", "UserGroup") @@ -34,7 +35,6 @@ def add_default_admin(apps, schema_editor): class Migration(migrations.Migration): - initial = True dependencies = [ @@ -49,13 +49,17 @@ class Migration(migrations.Migration): ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('is_active', models.BooleanField(default=True, + help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', + verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('username', models.CharField(max_length=20, unique=True, verbose_name='Username')), ('name', models.CharField(max_length=20, verbose_name='Name')), ('email', models.EmailField(max_length=30, unique=True, verbose_name='Email')), - ('role', models.CharField(blank=True, choices=[('Admin', 'Administrator'), ('User', 'User'), ('App', 'Application')], default='User', max_length=10, verbose_name='Role')), + ('role', models.CharField(blank=True, choices=[('Admin', 'Administrator'), ('User', 'User'), + ('App', 'Application')], default='User', max_length=10, + verbose_name='Role')), ('avatar', models.ImageField(null=True, upload_to='avatar', verbose_name='Avatar')), ('wechat', models.CharField(blank=True, max_length=30, verbose_name='Wechat')), ('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone')), @@ -63,9 +67,10 @@ class Migration(migrations.Migration): ('secret_key_otp', models.CharField(blank=True, max_length=16)), ('_private_key', models.CharField(blank=True, max_length=5000, verbose_name='Private key')), ('_public_key', models.CharField(blank=True, max_length=5000, verbose_name='Public key')), - ('comment', models.TextField(blank=True, max_length=200, verbose_name='Comment')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), ('is_first_login', models.BooleanField(default=False)), - ('date_expired', models.DateTimeField(blank=True, default=common.utils.date_expired_default, null=True, verbose_name='Date expired')), + ('date_expired', models.DateTimeField(blank=True, default=common.utils.date_expired_default, null=True, + verbose_name='Date expired')), ('created_by', models.CharField(default='', max_length=30, verbose_name='Created by')), ], options={ @@ -78,9 +83,11 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AccessKey', fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='AccessKeyID')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, + verbose_name='AccessKeyID')), ('secret', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='AccessKeySecret')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_key', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='access_key', + to=settings.AUTH_USER_MODEL, verbose_name='User')), ], ), migrations.CreateModel( @@ -88,7 +95,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('username', models.CharField(max_length=20, verbose_name='Username')), - ('type', models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')), + ('type', + models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')), ('ip', models.GenericIPAddressField(verbose_name='Login ip')), ('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')), ('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')), @@ -103,7 +111,8 @@ class Migration(migrations.Migration): fields=[ ('key', models.CharField(max_length=40, primary_key=True, serialize=False, verbose_name='Key')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='auth_token', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='auth_token', + to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ 'verbose_name': 'Private Token', @@ -127,12 +136,15 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='groups', - field=models.ManyToManyField(blank=True, related_name='users', to='users.UserGroup', verbose_name='User group'), + field=models.ManyToManyField(blank=True, related_name='users', to='users.UserGroup', + verbose_name='User group'), ), migrations.AddField( model_name='user', name='user_permissions', - field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), + field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', + related_name='user_set', related_query_name='user', to='auth.Permission', + verbose_name='user permissions'), ), migrations.RunPython(add_default_group), migrations.RunPython(add_default_admin), diff --git a/apps/users/migrations/0041_auto_20221220_1956.py b/apps/users/migrations/0041_auto_20221220_1956.py new file mode 100644 index 000000000..9ce1be2fe --- /dev/null +++ b/apps/users/migrations/0041_auto_20221220_1956.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.14 on 2022-12-20 11:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0040_alter_user_source'), + ] + + operations = [ + migrations.AddField( + model_name='usergroup', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='usergroup', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='usergroup', + name='comment', + field=models.TextField(blank=True, default='', verbose_name='Comment'), + ), + migrations.AlterField( + model_name='usergroup', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + ] diff --git a/apps/users/models/group.py b/apps/users/models/group.py index 2f50d24a0..e92e65c81 100644 --- a/apps/users/models/group.py +++ b/apps/users/models/group.py @@ -1,22 +1,16 @@ # -*- coding: utf-8 -*- -import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgModelMixin from common.utils import lazyproperty +from orgs.mixins.models import JMSOrgBaseModel __all__ = ['UserGroup'] -class UserGroup(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class UserGroup(JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_('Name')) - comment = models.TextField(blank=True, verbose_name=_('Comment')) - date_created = models.DateTimeField(auto_now_add=True, null=True, - verbose_name=_('Date created')) - created_by = models.CharField(max_length=100, null=True, blank=True) def __str__(self): return self.name @@ -27,7 +21,7 @@ class UserGroup(OrgModelMixin): class Meta: ordering = ['name'] - unique_together = [('org_id', 'name'),] + unique_together = [('org_id', 'name'), ] verbose_name = _("User group") @classmethod From 63d35ea8a6e4cc86d3864d74933e576d6ed2ec45 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Dec 2022 20:39:48 +0800 Subject: [PATCH 071/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset/common.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 72d494948..6ef10b019 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -3,7 +3,6 @@ # import logging -import uuid from collections import defaultdict from django.db import models @@ -52,7 +51,7 @@ class NodesRelationMixin: NODES_CACHE_KEY = 'ASSET_NODES_{}' ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES' CACHE_TIME = 3600 * 24 * 7 - id = "" + id: str _all_nodes_keys = None def get_nodes(self): @@ -99,7 +98,6 @@ class Protocol(models.Model): class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) address = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') From 327eb7a27dc125f051c6dced6d737211d33cb1b1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Dec 2022 10:17:28 +0800 Subject: [PATCH 072/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20=20migrati?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/migrations/0030_jobauditlog.py | 24 ------------------- .../ops/migrations/0031_auto_20221220_1956.py | 3 +-- 2 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 apps/ops/migrations/0030_jobauditlog.py diff --git a/apps/ops/migrations/0030_jobauditlog.py b/apps/ops/migrations/0030_jobauditlog.py deleted file mode 100644 index ff933e447..000000000 --- a/apps/ops/migrations/0030_jobauditlog.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.14 on 2022-12-20 07:24 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0029_auto_20221215_1712'), - ] - - operations = [ - migrations.CreateModel( - name='JobAuditLog', - fields=[ - ], - options={ - 'proxy': True, - 'indexes': [], - 'constraints': [], - }, - bases=('ops.jobexecution',), - ), - ] diff --git a/apps/ops/migrations/0031_auto_20221220_1956.py b/apps/ops/migrations/0031_auto_20221220_1956.py index 616d6f4f3..9cbc21547 100644 --- a/apps/ops/migrations/0031_auto_20221220_1956.py +++ b/apps/ops/migrations/0031_auto_20221220_1956.py @@ -4,9 +4,8 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('ops', '0030_jobauditlog'), + ('ops', '0030_auto_20221220_1941'), ] operations = [ From 3ddeb97ea5f76798e817ebc300093a8c9fa88fd9 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 20 Dec 2022 20:36:12 +0800 Subject: [PATCH 073/132] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=91=BD=E4=BB=A4=E5=BC=95=E5=8F=B7=E9=80=A0=E6=88=90?= =?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/ops/models/job.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 6f0c7f696..f554343ab 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -163,9 +163,10 @@ class JobExecution(JMSOrgBaseModel): def compile_shell(self): if self.current_job.type != 'adhoc': return - result = "{}{}{} ".format('\'', self.current_job.args, '\'') - result += "chdir={}".format(self.current_job.chdir) - return result + result = self.current_job.args + result += " chdir={}".format(self.current_job.chdir) + return self.job.args + # return result def get_runner(self): inv = self.current_job.inventory From 560ff651c46e1bbc87c99a97b3e334bd714502c8 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 20 Dec 2022 20:36:44 +0800 Subject: [PATCH 074/132] =?UTF-8?q?perf:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/job.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index f554343ab..5c8885221 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -166,7 +166,6 @@ class JobExecution(JMSOrgBaseModel): result = self.current_job.args result += " chdir={}".format(self.current_job.chdir) return self.job.args - # return result def get_runner(self): inv = self.current_job.inventory From f65146cd4531374aa522369c3f82fca8327540d0 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, 21 Dec 2022 11:21:48 +0800 Subject: [PATCH 075/132] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20mysql-cli?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 + Dockerfile.loong64 | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 6898a63c3..1dc3eacfa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ ARG TOOLS=" \ ca-certificates \ curl \ default-libmysqlclient-dev \ + default-mysql-client \ locales \ openssh-client \ sshpass \ diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 27002794f..455ee9fac 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -33,6 +33,7 @@ ARG TOOLS=" \ ca-certificates \ curl \ default-libmysqlclient-dev \ + default-mysql-client \ locales \ openssh-client \ sshpass \ From 3bef5825002d041e8f913556564343cbaccabb13 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, 21 Dec 2022 11:56:39 +0800 Subject: [PATCH 076/132] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20vim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 + Dockerfile.loong64 | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 1dc3eacfa..37f573be9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,6 +39,7 @@ ARG TOOLS=" \ sshpass \ telnet \ unzip \ + vim \ wget" ARG APT_MIRROR=http://mirrors.ustc.edu.cn diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 455ee9fac..5e942d5a3 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -39,6 +39,7 @@ ARG TOOLS=" \ sshpass \ telnet \ unzip \ + vim \ wget" RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ From 5198ac1cc0a2e20b73c635071844ae23cee2f5b1 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 21 Dec 2022 15:16:54 +0800 Subject: [PATCH 077/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20UserAssetG?= =?UTF-8?q?rantedTreeNodeRelation=20id=20=E4=B8=BA=20AutoField?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ops/migrations/0032_auto_20221221_1513.py | 23 +++++++++++++++++++ .../migrations/0034_auto_20221220_1956.py | 5 ---- apps/perms/models/perm_node.py | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 apps/ops/migrations/0032_auto_20221221_1513.py diff --git a/apps/ops/migrations/0032_auto_20221221_1513.py b/apps/ops/migrations/0032_auto_20221221_1513.py new file mode 100644 index 000000000..3a706f4b7 --- /dev/null +++ b/apps/ops/migrations/0032_auto_20221221_1513.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-12-21 07:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0031_auto_20221220_1956'), + ] + + operations = [ + migrations.AlterField( + model_name='historicaljob', + name='created_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='historicaljob', + name='updated_by', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by'), + ), + ] diff --git a/apps/perms/migrations/0034_auto_20221220_1956.py b/apps/perms/migrations/0034_auto_20221220_1956.py index 01b7f0f5f..41f6528d1 100644 --- a/apps/perms/migrations/0034_auto_20221220_1956.py +++ b/apps/perms/migrations/0034_auto_20221220_1956.py @@ -41,11 +41,6 @@ class Migration(migrations.Migration): name='created_by', field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by'), ), - migrations.AlterField( - model_name='userassetgrantedtreenoderelation', - name='id', - field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False), - ), migrations.AlterField( model_name='userassetgrantedtreenoderelation', name='updated_by', diff --git a/apps/perms/models/perm_node.py b/apps/perms/models/perm_node.py index 675e36e9c..89354a06b 100644 --- a/apps/perms/models/perm_node.py +++ b/apps/perms/models/perm_node.py @@ -16,6 +16,7 @@ class NodeFrom(TextChoices): class UserAssetGrantedTreeNodeRelation(FamilyMixin, JMSOrgBaseModel): NodeFrom = NodeFrom + id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=_('ID')) user = models.ForeignKey('users.User', db_constraint=False, on_delete=models.CASCADE) node = models.ForeignKey('assets.Node', default=None, on_delete=models.CASCADE, db_constraint=False, null=False, related_name='granted_node_rels') From 0e534f32514f5bb373b6a072947572234619d27f Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Wed, 21 Dec 2022 17:14:07 +0800 Subject: [PATCH 078/132] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/adhoc.py | 2 ++ apps/ops/api/job.py | 13 +++++++++---- apps/ops/api/playbook.py | 3 +++ apps/ops/serializers/job.py | 7 +++---- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 8caa59beb..aa7b890c5 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -15,6 +15,8 @@ class AdHocViewSet(OrgBulkModelViewSet): permission_classes = () model = AdHoc + def allow_bulk_destroy(self, qs, filtered): + return True def get_queryset(self): queryset = super().get_queryset() return queryset.filter(creator=self.request.user) diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index 9b36a5aba..0f37732bd 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -23,6 +23,9 @@ class JobViewSet(OrgBulkModelViewSet): permission_classes = () model = Job + def allow_bulk_destroy(self, qs, filtered): + return True + def get_queryset(self): queryset = super().get_queryset() queryset = queryset.filter(creator=self.request.user) @@ -31,20 +34,21 @@ class JobViewSet(OrgBulkModelViewSet): return queryset def perform_create(self, serializer): + run_after_save = serializer.validated_data.pop('run_after_save', False) instance = serializer.save() - run_after_save = serializer.validated_data.get('run_after_save', False) if instance.instant or run_after_save: self.run_job(instance, serializer) def perform_update(self, serializer): + run_after_save = serializer.validated_data.pop('run_after_save', False) instance = serializer.save() - run_after_save = serializer.validated_data.get('run_after_save', False) if run_after_save: self.run_job(instance, serializer) - @staticmethod - def run_job(job, serializer): + def run_job(self, job, serializer): execution = job.create_execution() + execution.creator = self.request.user + execution.save() task = run_ops_job_execution.delay(execution.id) set_task_to_serializer_data(serializer, task) @@ -58,6 +62,7 @@ class JobExecutionViewSet(OrgBulkModelViewSet): def perform_create(self, serializer): instance = serializer.save() instance.job_version = instance.job.version + instance.creator = self.request.user instance.save() task = run_ops_job_execution.delay(instance.id) set_task_to_serializer_data(serializer, task) diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index 009bfc2b8..aaafacd58 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -21,6 +21,9 @@ class PlaybookViewSet(OrgBulkModelViewSet): permission_classes = () model = Playbook + def allow_bulk_destroy(self, qs, filtered): + return True + def get_queryset(self): queryset = super().get_queryset() queryset = queryset.filter(creator=self.request.user) diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 005b88cdc..386c4e92f 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -9,12 +9,11 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) - run_after_save = serializers.BooleanField(label=_("Run after save"), read_only=True, default=False, required=False) + run_after_save = serializers.BooleanField(label=_("Run after save"), default=False, required=False) class Meta: model = Job - read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost", - "run_after_save"] + read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost"] fields = read_only_fields + [ "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "creator", "use_parameter_define", @@ -23,7 +22,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): "chdir", "comment", "summary", - "is_periodic", "interval", "crontab" + "is_periodic", "interval", "crontab", "run_after_save" ] From c304a58c059fc2876e82dd019795676576c0f23e Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:17:54 +0800 Subject: [PATCH 079/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9k8s=20?= =?UTF-8?q?=E6=A0=91=20(#9228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/assets/utils/k8s.py | 88 +++++++------------ .../serializers/connection_token.py | 4 + .../user_permission/tree/node_with_asset.py | 74 +++++++++------- 3 files changed, 79 insertions(+), 87 deletions(-) diff --git a/apps/assets/utils/k8s.py b/apps/assets/utils/k8s.py index 8e703d4b6..5ffd1612d 100644 --- a/apps/assets/utils/k8s.py +++ b/apps/assets/utils/k8s.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- from urllib3.exceptions import MaxRetryError -from urllib.parse import urlencode, parse_qsl +from urllib.parse import urlencode from kubernetes import client from kubernetes.client import api_client from kubernetes.client.api import core_v1_api from kubernetes.client.exceptions import ApiException -from rest_framework.generics import get_object_or_404 - from common.utils import get_logger -from assets.models import Account, Asset from ..const import CloudTypes, Category @@ -94,57 +91,45 @@ class KubernetesClient: return f'{gateway.address}:{gateway.port}' @classmethod - def get_kubernetes_data(cls, app_id, username): - asset = get_object_or_404(Asset, id=app_id) - account = get_object_or_404(Account, asset=asset, username=username) + def get_kubernetes_data(cls, asset, secret): k8s_url = f'{asset.address}:{asset.port}' proxy_url = cls.get_proxy_url(asset) - k8s = cls(k8s_url, account.secret, proxy=proxy_url) + k8s = cls(k8s_url, secret, proxy=proxy_url) return k8s.get_pods() class KubernetesTree: - def __init__(self, tree_id): - self.tree_id = str(tree_id) + def __init__(self, asset, secret): + self.asset = asset + self.secret = secret - @staticmethod - def create_tree_id(pid, tp, v): - i = dict(parse_qsl(pid)) - i[tp] = v - tree_id = urlencode(i) - return tree_id - - def as_tree_node(self, app): - pid = app.create_app_tree_pid(self.tree_id) - app_id = str(app.id) + def as_asset_tree_node(self): + i = str(self.asset.id) + name = str(self.asset) node = self.create_tree_node( - app_id, pid, app.name, 'k8s' + i, i, name, 'asset', is_open=True, ) return node - def as_asset_tree_node(self, asset): - i = urlencode({'asset_id': self.tree_id}) - node = self.create_tree_node( - i, str(asset.id), str(asset), 'asset', is_open=True, - ) + def as_namespace_node(self, name, tp, counts=0): + i = urlencode({'namespace': name}) + pid = str(self.asset.id) + name = f'{name}({counts})' + node = self.create_tree_node(i, pid, name, tp, icon='cloud') return node - def as_account_tree_node(self, account, parent_info): - username = account.username - name = str(account) - pid = urlencode({'asset_id': self.tree_id}) - i = self.create_tree_id(pid, 'account', username) - parent_info.update({'account': username}) - node = self.create_tree_node( - i, pid, name, 'account', icon='user-tie' - ) + def as_pod_tree_node(self, namespace, name, tp, counts=0): + pid = urlencode({'namespace': namespace}) + i = urlencode({'namespace': namespace, 'pod': name}) + name = f'{name}({counts})' + node = self.create_tree_node(i, pid, name, tp, icon='cloud') return node - def as_namespace_pod_tree_node(self, name, tp, counts=0, is_container=False): - i = self.create_tree_id(self.tree_id, tp, name) - name = name if is_container else f'{name}({counts})' + def as_container_tree_node(self, namespace, pod, name, tp): + pid = urlencode({'namespace': namespace, 'pod': pod}) + i = urlencode({'namespace': namespace, 'pod': pod, 'container': name}) node = self.create_tree_node( - i, self.tree_id, name, tp, icon='cloud', is_container=is_container + i, pid, name, tp, icon='cloud', is_container=True ) return node @@ -169,36 +154,31 @@ class KubernetesTree: } return node - def async_tree_node(self, parent_info): - pod_name = parent_info.get('pod') - asset_id = parent_info.get('asset_id') - namespace = parent_info.get('namespace') - account_username = parent_info.get('account') - + def async_tree_node(self, namespace, pod): tree = [] - data = KubernetesClient.get_kubernetes_data(asset_id, account_username) + data = KubernetesClient.get_kubernetes_data(self.asset, self.secret) if not data: return tree - if pod_name: + if pod: for container in next( filter( - lambda x: x['pod_name'] == pod_name, data[namespace] + lambda x: x['pod_name'] == pod, data[namespace] ) )['containers']: - container_node = self.as_namespace_pod_tree_node( - container, 'container', is_container=True + container_node = self.as_container_tree_node( + namespace, pod, container, 'container' ) tree.append(container_node) elif namespace: for pod in data[namespace]: - pod_nodes = self.as_namespace_pod_tree_node( - pod['pod_name'], 'pod', len(pod['containers']) + pod_nodes = self.as_pod_tree_node( + namespace, pod['pod_name'], 'pod', len(pod['containers']) ) tree.append(pod_nodes) - elif account_username: + else: for namespace, pods in data.items(): - namespace_node = self.as_namespace_pod_tree_node( + namespace_node = self.as_namespace_node( namespace, 'namespace', len(pods) ) tree.append(namespace_node) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index e45037853..2fd9ae16e 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField from orgs.mixins.serializers import OrgResourceModelSerializerMixin from ..models import ConnectionToken @@ -11,6 +12,9 @@ __all__ = [ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) + input_secret = EncryptedField( + label=_("Input secret"), max_length=40960, required=False, allow_blank=True + ) class Meta: model = ConnectionToken diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index adec9c079..fd8c8c233 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -3,14 +3,16 @@ from urllib.parse import parse_qsl from django.conf import settings from django.db.models import F, Value, CharField -from rest_framework.generics import ListAPIView -from rest_framework.generics import get_object_or_404 from rest_framework.request import Request from rest_framework.response import Response +from rest_framework.generics import ListAPIView +from rest_framework.generics import get_object_or_404 +from rest_framework.exceptions import PermissionDenied, NotFound -from assets.api import SerializeToTreeNodeMixin -from assets.models import Asset, Account from assets.utils import KubernetesTree +from assets.models import Asset, Account +from assets.api import SerializeToTreeNodeMixin +from authentication.models import ConnectionToken from common.utils import get_object_or_none, lazyproperty from common.utils.common import timeit from perms.hands import Node @@ -133,41 +135,47 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): """ 用户授权的K8s树 """ - @staticmethod - def asset(asset_id): - kwargs = {'id': asset_id, 'is_active': True} - asset = get_object_or_404(Asset, **kwargs) - return asset + def get_token(self): + token_id = self.request.query_params.get('token') + token = get_object_or_404(ConnectionToken, pk=token_id) + if token.is_expired: + raise PermissionDenied('Token is expired') + token.renewal() + return token - def get_accounts(self, asset): + def get_account_secret(self, token: ConnectionToken): util = PermAccountUtil() - accounts = util.get_permed_accounts_for_user(self.user, asset) - ignore_username = [Account.AliasAccount.INPUT, Account.AliasAccount.USER] - accounts = filter(lambda x: x.username not in ignore_username, accounts) + accounts = util.get_permed_accounts_for_user(self.user, token.asset) + account_username = token.account + accounts = filter(lambda x: x.username == account_username, accounts) accounts = list(accounts) - return accounts + if not accounts: + raise NotFound('Account is not found') + account = accounts[0] + if account.username in [ + Account.AliasAccount.INPUT, Account.AliasAccount.USER + ]: + return token.input_secret + else: + return account.secret + + def get_namespace_and_pod(self): + key = self.request.query_params.get('key') + namespace_and_pod = dict(parse_qsl(key)) + pod = namespace_and_pod.get('pod') + namespace = namespace_and_pod.get('namespace') + return namespace, pod def list(self, request: Request, *args, **kwargs): - tree_id = request.query_params.get('tree_id') - key = request.query_params.get('key', {}) + token = self.get_token() + asset = token.asset + secret = self.get_account_secret(token) + namespace, pod = self.get_namespace_and_pod() tree = [] - parent_info = dict(parse_qsl(key)) - account_username = parent_info.get('account') - - asset_id = parent_info.get('asset_id') - asset_id = tree_id if not asset_id else asset_id - - if tree_id and not key and not account_username: - asset = self.asset(asset_id) - accounts = self.get_accounts(asset) - asset_node = KubernetesTree(tree_id).as_asset_tree_node(asset) + k8s_tree_instance = KubernetesTree(asset, secret) + if not any([namespace, pod]): + asset_node = k8s_tree_instance.as_asset_tree_node() tree.append(asset_node) - for account in accounts: - account_node = KubernetesTree(tree_id).as_account_tree_node( - account, parent_info, - ) - tree.append(account_node) - elif key and account_username: - tree = KubernetesTree(key).async_tree_node(parent_info) + tree.extend(k8s_tree_instance.async_tree_node(namespace, pod)) return Response(data=tree) From 510ca9a5b855dacbdbcce7fb5a68bfd6977e12fd Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 21 Dec 2022 17:32:55 +0800 Subject: [PATCH 080/132] perf: k8s tree --- apps/perms/api/user_permission/tree/node_with_asset.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index fd8c8c233..f65cfc0b1 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -159,8 +159,8 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): else: return account.secret - def get_namespace_and_pod(self): - key = self.request.query_params.get('key') + @staticmethod + def get_namespace_and_pod(key): namespace_and_pod = dict(parse_qsl(key)) pod = namespace_and_pod.get('pod') namespace = namespace_and_pod.get('namespace') @@ -170,11 +170,12 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): token = self.get_token() asset = token.asset secret = self.get_account_secret(token) - namespace, pod = self.get_namespace_and_pod() + key = self.request.query_params.get('key') + namespace, pod = self.get_namespace_and_pod(key) tree = [] k8s_tree_instance = KubernetesTree(asset, secret) - if not any([namespace, pod]): + if not any([namespace, pod]) and not key: asset_node = k8s_tree_instance.as_asset_tree_node() tree.append(asset_node) tree.extend(k8s_tree_instance.async_tree_node(namespace, pod)) From 34cc3b233d5bb94b31b943ef918be4778f2f1508 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 21 Dec 2022 17:36:44 +0800 Subject: [PATCH 081/132] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E8=B5=84=E4=BA=A7=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=B7=A5=E5=85=B7(=E9=87=8D=E6=9E=84=E4=B8=AD..)=20(#9225)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 重构用户授权资产查询工具(重构中..) * perf: 修改 get_perm_nodes_assets 名称 * refactor: 优化用户授权节点查询工具; 删除UnionQuerySet工具 Co-authored-by: Bai --- apps/common/db/models.py | 84 +----- apps/perms/api/asset_permission_relation.py | 5 +- apps/perms/api/user_permission/assets.py | 10 +- apps/perms/api/user_permission/nodes.py | 4 +- .../user_permission/tree/node_with_asset.py | 26 +- apps/perms/filters.py | 9 +- apps/perms/models/asset_permission.py | 15 +- apps/perms/models/perm_node.py | 30 +- apps/perms/utils/__init__.py | 2 +- apps/perms/utils/user_perm.py | 219 ++++++++++++++ apps/perms/utils/user_permission.py | 280 ------------------ 11 files changed, 269 insertions(+), 415 deletions(-) create mode 100644 apps/perms/utils/user_perm.py delete mode 100644 apps/perms/utils/user_permission.py diff --git a/apps/common/db/models.py b/apps/common/db/models.py index 7be495538..ae726b85d 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -9,9 +9,8 @@ - 此文件中添加代码的时候,注意不要跟 `django.db.models` 中的命名冲突 """ -import inspect import uuid -from functools import reduce, partial +from functools import reduce from django.db import models from django.db import transaction @@ -96,87 +95,6 @@ def output_as_string(field_name): return ExpressionWrapper(F(field_name), output_field=models.CharField()) -class UnionQuerySet(QuerySet): - after_union = ['order_by'] - not_return_qs = [ - 'query', 'get', 'create', 'get_or_create', - 'update_or_create', 'bulk_create', 'count', - 'latest', 'earliest', 'first', 'last', 'aggregate', - 'exists', 'update', 'delete', 'as_manager', 'explain', - ] - - def __init__(self, *queryset_list): - self.queryset_list = queryset_list - self.after_union_items = [] - self.before_union_items = [] - - def __execute(self): - queryset_list = [] - for qs in self.queryset_list: - for attr, args, kwargs in self.before_union_items: - qs = getattr(qs, attr)(*args, **kwargs) - queryset_list.append(qs) - union_qs = reduce(lambda x, y: x.union(y), queryset_list) - for attr, args, kwargs in self.after_union_items: - union_qs = getattr(union_qs, attr)(*args, **kwargs) - return union_qs - - def __before_union_perform(self, item, *args, **kwargs): - self.before_union_items.append((item, args, kwargs)) - return self.__clone(*self.queryset_list) - - def __after_union_perform(self, item, *args, **kwargs): - self.after_union_items.append((item, args, kwargs)) - return self.__clone(*self.queryset_list) - - def __clone(self, *queryset_list): - uqs = UnionQuerySet(*queryset_list) - uqs.after_union_items = self.after_union_items - uqs.before_union_items = self.before_union_items - return uqs - - def __getattribute__(self, item): - if item.startswith('__') or item in UnionQuerySet.__dict__ or item in [ - 'queryset_list', 'after_union_items', 'before_union_items' - ]: - return object.__getattribute__(self, item) - - if item in UnionQuerySet.not_return_qs: - return getattr(self.__execute(), item) - - origin_item = object.__getattribute__(self, 'queryset_list')[0] - origin_attr = getattr(origin_item, item, None) - if not inspect.ismethod(origin_attr): - return getattr(self.__execute(), item) - - if item in UnionQuerySet.after_union: - attr = partial(self.__after_union_perform, item) - else: - attr = partial(self.__before_union_perform, item) - return attr - - def __getitem__(self, item): - return self.__execute()[item] - - def __iter__(self): - return iter(self.__execute()) - - def __str__(self): - return str(self.__execute()) - - def __repr__(self): - return repr(self.__execute()) - - @classmethod - def test_it(cls): - from assets.models import Asset - assets1 = Asset.objects.filter(hostname__startswith='a') - assets2 = Asset.objects.filter(hostname__startswith='b') - - qs = cls(assets1, assets2) - return qs - - class MultiTableChildQueryset(QuerySet): def bulk_create(self, objs, batch_size=None): diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index c2c116248..5b85cb971 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -9,7 +9,7 @@ from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org from perms import serializers from perms import models -from perms.utils.user_permission import UserGrantedAssetsQueryUtils +from perms.utils import AssetPermissionPermAssetUtil from assets.serializers import AccountSerializer __all__ = [ @@ -95,8 +95,7 @@ class AssetPermissionAllAssetListApi(generics.ListAPIView): def get_queryset(self): pk = self.kwargs.get("pk") - query_utils = UserGrantedAssetsQueryUtils(None, asset_perm_ids=[pk]) - assets = query_utils.get_all_granted_assets() + assets = AssetPermissionPermAssetUtil(perm_ids=[pk]).get_all_assets() return assets diff --git a/apps/perms/api/user_permission/assets.py b/apps/perms/api/user_permission/assets.py index e494b7a4a..e499a3127 100644 --- a/apps/perms/api/user_permission/assets.py +++ b/apps/perms/api/user_permission/assets.py @@ -6,7 +6,7 @@ from assets.api.asset.asset import AssetFilterSet from perms import serializers from perms.pagination import AllPermedAssetPagination from perms.pagination import NodePermedAssetPagination -from perms.utils.user_permission import UserGrantedAssetsQueryUtils +from perms.utils import UserPermAssetUtil from common.utils import get_logger, lazyproperty from .mixin import ( @@ -43,21 +43,23 @@ class BaseUserPermedAssetsApi(SelfOrPKUserMixin, ListAPIView): def get_assets(self): return Asset.objects.none() + query_asset_util: UserPermAssetUtil + @lazyproperty def query_asset_util(self): - return UserGrantedAssetsQueryUtils(self.user) + return UserPermAssetUtil(self.user) class UserAllPermedAssetsApi(BaseUserPermedAssetsApi): pagination_class = AllPermedAssetPagination def get_assets(self): - return self.query_asset_util.get_all_granted_assets() + return self.query_asset_util.get_all_assets() class UserDirectPermedAssetsApi(BaseUserPermedAssetsApi): def get_assets(self): - return self.query_asset_util.get_direct_granted_assets() + return self.query_asset_util.get_direct_assets() class UserFavoriteAssetsApi(BaseUserPermedAssetsApi): diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index 1972909e9..793ad3858 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -7,7 +7,7 @@ from rest_framework.generics import ListAPIView from assets.models import Node from common.utils import get_logger, lazyproperty from perms import serializers -from perms.utils.user_permission import UserGrantedNodesQueryUtils +from perms.utils import UserPermNodeUtil from .mixin import SelfOrPKUserMixin logger = get_logger(__name__) @@ -32,7 +32,7 @@ class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView): @lazyproperty def query_node_util(self): - return UserGrantedNodesQueryUtils(self.user) + return UserPermNodeUtil(self.user) class UserAllPermedNodesApi(BaseUserPermedNodesApi): diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index f65cfc0b1..204859f36 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -17,11 +17,8 @@ from common.utils import get_object_or_none, lazyproperty from common.utils.common import timeit from perms.hands import Node from perms.models import PermNode -from perms.utils import PermAccountUtil -from perms.utils.permission import AssetPermissionUtil -from perms.utils.user_permission import ( - UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, -) +from perms.utils import PermAccountUtil, UserPermNodeUtil, AssetPermissionUtil +from perms.utils import UserPermAssetUtil from .mixin import RebuildTreeMixin from ..mixin import SelfOrPKUserMixin @@ -54,13 +51,12 @@ class BaseUserNodeWithAssetAsTreeApi( class UserPermedNodesWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): - query_node_util: UserGrantedNodesQueryUtils - query_asset_util: UserGrantedAssetsQueryUtils + query_node_util: UserPermNodeUtil + query_asset_util: UserPermAssetUtil def get_nodes_assets(self): - perm_ids = AssetPermissionUtil().get_permissions_for_user(self.request.user, flat=True) - self.query_node_util = UserGrantedNodesQueryUtils(self.request.user, perm_ids) - self.query_asset_util = UserGrantedAssetsQueryUtils(self.request.user, perm_ids) + self.query_node_util = UserPermNodeUtil(self.request.user) + self.query_asset_util = UserPermAssetUtil(self.request.user) ung_nodes, ung_assets = self._get_nodes_assets_for_ungrouped() fav_nodes, fav_assets = self._get_nodes_assets_for_favorite() all_nodes, all_assets = self._get_nodes_assets_for_all() @@ -89,9 +85,9 @@ class UserPermedNodesWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): def _get_nodes_assets_for_all(self): nodes = self.query_node_util.get_whole_tree_nodes(with_special=False) if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - assets = self.query_asset_util.get_direct_granted_nodes_assets() + assets = self.query_asset_util.get_perm_nodes_assets() else: - assets = self.query_asset_util.get_all_granted_assets() + assets = self.query_asset_util.get_all_assets() assets = assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform') return nodes, assets @@ -102,8 +98,8 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): def get_nodes_assets(self): nodes = PermNode.objects.none() assets = Asset.objects.none() - query_node_util = UserGrantedNodesQueryUtils(self.user) - query_asset_util = UserGrantedAssetsQueryUtils(self.user) + query_node_util = UserPermNodeUtil(self.user) + query_asset_util = UserPermAssetUtil(self.user) node_key = self.query_node_key if not node_key: nodes = query_node_util.get_top_level_nodes() @@ -113,7 +109,7 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): assets = query_asset_util.get_favorite_assets() else: nodes = query_node_util.get_node_children(node_key) - assets = query_asset_util.get_node_assets(node_key) + assets = query_asset_util.get_node_assets(key=node_key) assets = assets.prefetch_related('platform') return nodes, assets diff --git a/apps/perms/filters.py b/apps/perms/filters.py index 05f202655..8743c38be 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -1,7 +1,6 @@ from django_filters import rest_framework as filters from django.db.models import QuerySet, Q -from common.db.models import UnionQuerySet from common.drf.filters import BaseFilterSet from common.utils import get_object_or_none from users.models import User, UserGroup @@ -169,10 +168,10 @@ class AssetPermissionFilter(PermissionBaseFilter): inherit_all_node_ids = Node.objects.filter(key__in=inherit_all_node_keys).values_list('id', flat=True) inherit_all_node_ids = list(inherit_all_node_ids) - qs1 = queryset.filter(assets__in=asset_ids).distinct() - qs2 = queryset.filter(nodes__in=inherit_all_node_ids).distinct() - - qs = UnionQuerySet(qs1, qs2) + qs1_ids = queryset.filter(assets__in=asset_ids).distinct().values_list('id', flat=True) + qs2_ids = queryset.filter(nodes__in=inherit_all_node_ids).distinct().values_list('id', flat=True) + qs_ids = list(qs1_ids) + list(qs2_ids) + qs = queryset.filter(id__in=qs_ids) return qs def filter_effective(self, queryset): diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index ac6fecdb4..989f10cbc 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -5,14 +5,14 @@ from django.db.models import Q from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from users.models import User from assets.models import Asset, Account -from common.db.models import UnionQuerySet -from common.utils import date_expired_default -from common.utils.timezone import local_now from orgs.mixins.models import JMSOrgBaseModel from orgs.mixins.models import OrgManager +from common.utils import date_expired_default +from common.utils.timezone import local_now + from perms.const import ActionChoices -from users.models import User __all__ = ['AssetPermission', 'ActionChoices'] @@ -104,9 +104,10 @@ class AssetPermission(JMSOrgBaseModel): group_ids = self.user_groups.all().values_list('id', flat=True) user_ids = list(user_ids) group_ids = list(group_ids) - qs1 = User.objects.filter(id__in=user_ids).distinct() - qs2 = User.objects.filter(groups__id__in=group_ids).distinct() - qs = UnionQuerySet(qs1, qs2) + qs1_ids = User.objects.filter(id__in=user_ids).distinct().values_list('id', flat=True) + qs2_ids = User.objects.filter(groups__id__in=group_ids).distinct().values_list('id', flat=True) + qs_ids = list(qs1_ids) + list(qs2_ids) + qs = User.objects.filter(id__in=qs_ids) return qs def get_all_assets(self, flat=False): diff --git a/apps/perms/models/perm_node.py b/apps/perms/models/perm_node.py index 89354a06b..ed4b4b213 100644 --- a/apps/perms/models/perm_node.py +++ b/apps/perms/models/perm_node.py @@ -36,15 +36,14 @@ class UserAssetGrantedTreeNodeRelation(FamilyMixin, JMSOrgBaseModel): return self.node_parent_key @classmethod - def get_node_granted_status(cls, user, key): + def get_node_from_with_node(cls, user, key): ancestor_keys = set(cls.get_node_ancestor_keys(key, with_self=True)) - ancestor_rel_nodes = cls.objects.filter(user=user, node_key__in=ancestor_keys) - - for rel_node in ancestor_rel_nodes: - if rel_node.key == key: - return rel_node.node_from, rel_node - if rel_node.node_from == cls.NodeFrom.granted: - return cls.NodeFrom.granted, None + ancestor_nodes = cls.objects.filter(user=user, node_key__in=ancestor_keys) + for node in ancestor_nodes: + if node.key == key: + return node.node_from, node + if node.node_from == cls.NodeFrom.granted: + return node.node_from, None return '', None @@ -91,15 +90,16 @@ class PermNode(Node): node.assets_amount = assets_amount return node - def get_granted_status(self, user): - status, rel_node = UserAssetGrantedTreeNodeRelation.get_node_granted_status(user, self.key) - self.node_from = status - if rel_node: - self.granted_assets_amount = rel_node.node_assets_amount - return status + def compute_node_from_and_assets_amount(self, user): + node_from, node = UserAssetGrantedTreeNodeRelation.get_node_from_with_node( + user, self.key + ) + self.node_from = node_from + if node: + self.granted_assets_amount = node.node_assets_amount def save(self): - # 这是个只读 Model + """ 这是个只读 Model """ raise NotImplementedError diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py index 8563b21e9..808bdd1b8 100644 --- a/apps/perms/utils/__init__.py +++ b/apps/perms/utils/__init__.py @@ -1,4 +1,4 @@ from .permission import * -from .user_permission import * from .account import * from .user_perm_tree import * +from .user_perm import * diff --git a/apps/perms/utils/user_perm.py b/apps/perms/utils/user_perm.py new file mode 100644 index 000000000..a5f956de8 --- /dev/null +++ b/apps/perms/utils/user_perm.py @@ -0,0 +1,219 @@ +from assets.models import FavoriteAsset, Asset + +from django.conf import settings +from django.db.models import Q + +from common.utils.common import timeit + +from perms.models import AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation + +from .permission import AssetPermissionUtil + + +__all__ = ['AssetPermissionPermAssetUtil', 'UserPermAssetUtil', 'UserPermNodeUtil'] + + +class AssetPermissionPermAssetUtil: + + def __init__(self, perm_ids): + self.perm_ids = perm_ids + + def get_all_assets(self): + """ 获取所有授权的资产 """ + node_asset_ids = self.get_perm_nodes_assets(flat=True) + direct_asset_ids = self.get_direct_assets(flat=True) + asset_ids = list(node_asset_ids) + list(direct_asset_ids) + assets = Asset.objects.filter(id__in=asset_ids) + return assets + + def get_perm_nodes_assets(self, flat=False): + """ 获取所有授权节点下的资产 """ + node_ids = AssetPermission.nodes.through.objects \ + .filter(assetpermission_id__in=self.perm_ids) \ + .values_list('node_id', flat=True) \ + .distinct() + node_ids = list(node_ids) + nodes = PermNode.objects.filter(id__in=node_ids).only('id', 'key') + assets = PermNode.get_nodes_all_assets(*nodes) + if flat: + return assets.values_list('id', flat=True) + return assets + + def get_direct_assets(self, flat=False): + """ 获取直接授权的资产 """ + assets = Asset.objects.order_by() \ + .filter(granted_by_permissions__id__in=self.perm_ids) \ + .distinct() + if flat: + return assets.values_list('id', flat=True) + return assets + + +class UserPermAssetUtil(AssetPermissionPermAssetUtil): + + def __init__(self, user): + self.user = user + perm_ids = AssetPermissionUtil().get_permissions_for_user(self.user, flat=True) + super().__init__(perm_ids) + + def get_ungroup_assets(self): + return self.get_direct_assets() + + def get_favorite_assets(self): + assets = self.get_all_assets() + asset_ids = FavoriteAsset.objects.filter(user=self.user).values_list('asset_id', flat=True) + assets = assets.filter(id__in=list(asset_ids)) + return assets + + def get_node_assets(self, key): + node = PermNode.objects.get(key=key) + node.compute_node_from_and_assets_amount(self.user) + if node.node_from == node.NodeFrom.granted: + assets = Asset.objects.filter(nodes__id=node.id).order_by() + elif node.node_from == node.NodeFrom.asset: + assets = self._get_indirect_perm_node_assets(node) + else: + assets = Asset.objects.none() + assets = assets.order_by('name') + return assets + + def get_node_all_assets(self, node_id): + """ 获取节点下的所有资产 """ + node = PermNode.objects.get(id=node_id) + node.compute_node_from_and_assets_amount(self.user) + if node.node_from == node.NodeFrom.granted: + assets = PermNode.get_nodes_all_assets() + elif node.node_from in (node.NodeFrom.asset, node.NodeFrom.child): + node.assets_amount = node.granted_assets_amount + assets = self._get_indirect_perm_node_all_assets(node) + else: + node.assets_amount = 0 + assets = Asset.objects.none() + return node, assets + + def _get_indirect_perm_node_assets(self, node): + """ 获取间接授权节点下的直接资产 """ + assets = self.get_direct_assets() + assets = assets.filter(nodes__id=node.id).order_by().distinct() + return assets + + def _get_indirect_perm_node_all_assets(self, node): + """ 获取间接授权节点下的所有资产 + 此算法依据 `UserAssetGrantedTreeNodeRelation` 的数据查询 + 1. 查询该节点下的直接授权节点 + 2. 查询该节点下授权资产关联的节点 + """ + # 查询节点下直接授权的子节点 + asset_ids = set() + children_from_granted = UserAssetGrantedTreeNodeRelation.objects \ + .filter(user=self.user) \ + .filter(node_key__startwith=f'{node.key}:', node_from=node.NodeFrom.granted) \ + .only('node_id', 'node_key') + for n in children_from_granted: + n.id = n.node_id + _assets = PermNode.get_nodes_all_assets(*children_from_granted) + _asset_ids = _assets.values_list('id', flat=True) + asset_ids.update(list(_asset_ids)) + + # 查询节点下资产授权的节点 + children_from_assets = UserAssetGrantedTreeNodeRelation.objects \ + .filter(user=self.user) \ + .filter(node_key__startwith=f'{node.key}:', node_from=node.NodeFrom.asset) \ + .values_list('node_id', flat=True) + children_from_assets = set(children_from_assets) + if node.node_from == node.NodeFrom.asset: + children_from_assets.add(node.id) + _asset_ids = Asset.objects \ + .filter(node__id__in=children_from_assets) \ + .filter(granted_by_permissions__id__in=self.perm_ids) \ + .distinct() \ + .order_by() \ + .values_list('id', flat=True) + asset_ids.update(list(_asset_ids)) + + return Asset.objects.filter(id__in=asset_ids) + + +class UserPermNodeUtil: + + def __init__(self, user): + self.user = user + self.perm_ids = AssetPermissionUtil().get_permissions_for_user(self.user, flat=True) + + def get_favorite_node(self): + assets_amount = UserPermAssetUtil(self.user).get_favorite_assets().count() + return PermNode.get_favorite_node(assets_amount) + + def get_ungrouped_node(self): + assets_amount = UserPermAssetUtil(self.user).get_direct_assets().count() + return PermNode.get_favorite_node(assets_amount) + + def get_top_level_nodes(self): + nodes = self.get_special_nodes() + real_nodes = self._get_indirect_perm_node_children(key='') + if len(real_nodes) == 1: + children = self.get_node_children(real_nodes[0].key) + nodes.extend(children) + return nodes + + def get_special_nodes(self): + nodes = [] + if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + ung_node = self.get_ungrouped_node() + nodes.append(ung_node) + fav_node = self.get_favorite_node() + nodes.append(fav_node) + return nodes + + def get_node_children(self, key): + if not key: + return self.get_top_level_nodes() + + if key in [PermNode.FAVORITE_NODE_KEY, PermNode.UNGROUPED_NODE_KEY]: + return PermNode.objects.none() + + node = PermNode.objects.get(key=key) + node.compute_node_from_and_assets_amount(self.user) + if node.node_from == node.NodeFrom.granted: + children = PermNode.objects.filter(parent_key=key) + elif node.node_from in (node.NodeFrom.asset, node.NodeFrom.child): + children = self._get_indirect_perm_node_children(key) + else: + children = PermNode.objects.none() + children = sorted(children, key=lambda x: x.value) + return children + + def _get_indirect_perm_node_children(self, key): + """ 获取未直接授权节点的子节点 """ + children = PermNode.objects.filter(granted_node_rels__user=self.user, parent_key=key) + children = children.annotate(**PermNode.annotate_granted_node_rel_fields).distinct() + for node in children: + node.assets_amount = node.granted_assets_amount + return children + + @timeit + def get_whole_tree_nodes(self, with_special=True): + user_nodes = PermNode.objects.filter(granted_node_rels__user=self.user) + user_nodes = user_nodes.annotate(**PermNode.annotate_granted_node_rel_fields).distinct() + + key_node_mapper = {} + q_nodes_descendant = Q() + for node in user_nodes: + node.assets_amount = node.granted_assets_amount + key_node_mapper[node.key] = node + if node.node_from == node.NodeFrom.granted: + """ 直接授权的节点, 增加后代节点的过滤条件 """ + q_nodes_descendant |= Q(key__startswith=f'{node.key}:') + if q_nodes_descendant: + descendant_nodes = PermNode.objects.filter(q_nodes_descendant) + for node in descendant_nodes: + key_node_mapper[node.key] = node + + nodes = [] + if with_special: + special_nodes = self.get_special_nodes() + nodes.extend(special_nodes) + nodes.extend(list(key_node_mapper.values())) + + return nodes + diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py deleted file mode 100644 index 8ee5992dd..000000000 --- a/apps/perms/utils/user_permission.py +++ /dev/null @@ -1,280 +0,0 @@ -from collections import defaultdict -from typing import List, Tuple - -from django.conf import settings -from django.db.models import Q, QuerySet -from django.utils.translation import gettext as _ - -from users.models import User -from assets.utils import NodeAssetsUtil -from assets.models import ( - Asset, - FavoriteAsset, - AssetQuerySet, - NodeQuerySet -) -from orgs.utils import ( - tmp_to_org, - current_org, - ensure_in_real_or_default_org, -) -from common.db.models import output_as_string, UnionQuerySet -from common.utils import get_logger -from common.utils.common import lazyproperty, timeit - -from perms.models import ( - AssetPermission, - PermNode, - UserAssetGrantedTreeNodeRelation -) -from .permission import AssetPermissionUtil - -NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom -NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id') - -logger = get_logger(__name__) - - -class UserGrantedUtilsBase: - user: User - - def __init__(self, user, asset_perm_ids=None): - self.user = user - self._asset_perm_ids = asset_perm_ids and set(asset_perm_ids) - - @lazyproperty - def asset_perm_ids(self) -> set: - if self._asset_perm_ids: - return self._asset_perm_ids - - asset_perm_ids = AssetPermissionUtil().get_permissions_for_user(self.user, flat=True) - return asset_perm_ids - - -class UserGrantedAssetsQueryUtils(UserGrantedUtilsBase): - - def get_favorite_assets(self) -> QuerySet: - assets = self.get_all_granted_assets() - asset_ids = FavoriteAsset.objects.filter(user=self.user).values_list('asset_id', flat=True) - assets = assets.filter(id__in=list(asset_ids)) - return assets - - def get_ungroup_assets(self) -> AssetQuerySet: - return self.get_direct_granted_assets() - - def get_direct_granted_assets(self) -> AssetQuerySet: - queryset = Asset.objects.order_by().filter( - granted_by_permissions__id__in=self.asset_perm_ids - ).distinct() - return queryset - - def get_direct_granted_nodes_assets(self) -> AssetQuerySet: - granted_node_ids = AssetPermission.nodes.through.objects.filter( - assetpermission_id__in=self.asset_perm_ids - ).values_list('node_id', flat=True).distinct() - granted_node_ids = list(granted_node_ids) - granted_nodes = PermNode.objects.filter(id__in=granted_node_ids).only('id', 'key') - queryset = PermNode.get_nodes_all_assets(*granted_nodes) - return queryset - - def get_all_granted_assets(self) -> QuerySet: - nodes_assets = self.get_direct_granted_nodes_assets() - assets = self.get_direct_granted_assets() - # queryset = UnionQuerySet(nodes_assets, assets) - # return queryset - node_asset_ids = nodes_assets.values_list('id', flat=True) - direct_asset_ids = assets.values_list('id', flat=True) - asset_ids = list(node_asset_ids) + list(direct_asset_ids) - asset = Asset.objects.filter(id__in=asset_ids) - return asset - - def get_node_all_assets(self, id) -> Tuple[PermNode, QuerySet]: - node = PermNode.objects.get(id=id) - granted_status = node.get_granted_status(self.user) - if granted_status == NodeFrom.granted: - assets = PermNode.get_nodes_all_assets(node) - return node, assets - elif granted_status in (NodeFrom.asset, NodeFrom.child): - node.use_granted_assets_amount() - assets = self._get_indirect_granted_node_all_assets(node) - return node, assets - else: - node.assets_amount = 0 - return node, Asset.objects.none() - - def get_node_assets(self, key) -> AssetQuerySet: - node = PermNode.objects.get(key=key) - granted_status = node.get_granted_status(self.user) - - if granted_status == NodeFrom.granted: - assets = Asset.objects.order_by().filter(nodes__id=node.id) - elif granted_status == NodeFrom.asset: - assets = self._get_indirect_granted_node_assets(node.id) - else: - assets = Asset.objects.none() - assets = assets.order_by('name') - return assets - - def _get_indirect_granted_node_assets(self, id) -> AssetQuerySet: - assets = Asset.objects.order_by().filter(nodes__id=id).distinct() & self.get_direct_granted_assets() - return assets - - def _get_indirect_granted_node_all_assets(self, node) -> QuerySet: - """ - 此算法依据 `UserAssetGrantedTreeNodeRelation` 的数据查询 - 1. 查询该节点下的直接授权节点 - 2. 查询该节点下授权资产关联的节点 - """ - user = self.user - - # 查询该节点下的授权节点 - granted_nodes = UserAssetGrantedTreeNodeRelation.objects.filter( - user=user, node_from=NodeFrom.granted - ).filter( - Q(node_key__startswith=f'{node.key}:') - ).only('node_id', 'node_key') - - for n in granted_nodes: - n.id = n.node_id - - node_assets = PermNode.get_nodes_all_assets(*granted_nodes) - - # 查询该节点下的资产授权节点 - only_asset_granted_node_ids = UserAssetGrantedTreeNodeRelation.objects.filter( - user=user, node_from=NodeFrom.asset - ).filter( - Q(node_key__startswith=f'{node.key}:') - ).values_list('node_id', flat=True) - - only_asset_granted_node_ids = list(only_asset_granted_node_ids) - if node.node_from == NodeFrom.asset: - only_asset_granted_node_ids.append(node.id) - - assets = Asset.objects.filter( - nodes__id__in=only_asset_granted_node_ids, - granted_by_permissions__id__in=self.asset_perm_ids - ).distinct().order_by() - granted_assets = UnionQuerySet(node_assets, assets) - return granted_assets - - -class UserGrantedNodesQueryUtils(UserGrantedUtilsBase): - def sort(self, nodes): - nodes = sorted(nodes, key=lambda x: x.value) - return nodes - - def get_node_children(self, key): - if not key: - return self.get_top_level_nodes() - - nodes = PermNode.objects.none() - if key in [PermNode.FAVORITE_NODE_KEY, PermNode.UNGROUPED_NODE_KEY]: - return nodes - - node = PermNode.objects.get(key=key) - granted_status = node.get_granted_status(self.user) - if granted_status == NodeFrom.granted: - nodes = PermNode.objects.filter(parent_key=key) - elif granted_status in (NodeFrom.asset, NodeFrom.child): - nodes = self.get_indirect_granted_node_children(key) - nodes = self.sort(nodes) - return nodes - - def get_indirect_granted_node_children(self, key): - """ - 获取用户授权树中未授权节点的子节点 - 只匹配在 `UserAssetGrantedTreeNodeRelation` 中存在的节点 - """ - user = self.user - nodes = PermNode.objects.filter( - granted_node_rels__user=user, - parent_key=key - ).annotate( - **PermNode.annotate_granted_node_rel_fields - ).distinct() - - # 设置节点授权资产数量 - for node in nodes: - node.use_granted_assets_amount() - return nodes - - def get_top_level_nodes(self): - nodes = self.get_special_nodes() - real_nodes = self.get_indirect_granted_node_children('') - nodes.extend(real_nodes) - if len(real_nodes) == 1: - children = self.get_node_children(real_nodes[0].key) - nodes.extend(children) - return nodes - - def get_ungrouped_node(self): - assets_util = UserGrantedAssetsQueryUtils(self.user, self.asset_perm_ids) - assets_amount = assets_util.get_direct_granted_assets().count() - return PermNode.get_ungrouped_node(assets_amount) - - def get_favorite_node(self): - assets_query_utils = UserGrantedAssetsQueryUtils(self.user, self.asset_perm_ids) - assets_amount = assets_query_utils.get_favorite_assets().values_list('id').count() - return PermNode.get_favorite_node(assets_amount) - - @staticmethod - def get_root_node(): - name = _('My assets') - node = { - 'id': '', - 'name': name, - 'title': name, - 'pId': '', - 'open': True, - 'isParent': True, - 'meta': { - 'type': 'root' - } - } - return node - - def get_special_nodes(self): - nodes = [] - if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - ungrouped_node = self.get_ungrouped_node() - nodes.append(ungrouped_node) - favorite_node = self.get_favorite_node() - nodes.append(favorite_node) - return nodes - - @timeit - def get_whole_tree_nodes(self, with_special=True): - """ - 这里的 granted nodes, 是整棵树需要的node,推算出来的也算 - :param with_special: - :return: - """ - nodes = PermNode.objects.filter(granted_node_rels__user=self.user) \ - .annotate(**PermNode.annotate_granted_node_rel_fields) \ - .distinct() - - key_to_node_mapper = {} - nodes_descendant_q = Q() - - for node in nodes: - node.use_granted_assets_amount() - key_to_node_mapper[node.key] = node - - if node.node_from == NodeFrom.granted: - # 直接授权的节点 - # 增加查询后代节点的过滤条件 - nodes_descendant_q |= Q(key__startswith=f'{node.key}:') - - if nodes_descendant_q: - descendant_nodes = PermNode.objects.filter( - nodes_descendant_q - ) - for node in descendant_nodes: - key_to_node_mapper[node.key] = node - - all_nodes = [] - if with_special: - special_nodes = self.get_special_nodes() - all_nodes.extend(special_nodes) - all_nodes.extend(key_to_node_mapper.values()) - return all_nodes From d4e215aeaa71605c87a5555b35cc8161cb00bd9d Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 21 Dec 2022 18:00:50 +0800 Subject: [PATCH 082/132] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dluna=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E8=B5=84=E4=BA=A7=E6=A0=91=E5=8A=A0=E8=BD=BD=E4=B8=8D?= =?UTF-8?q?=E5=87=BA=E6=9D=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user_permission/tree/node_with_asset.py | 5 +++-- apps/perms/models/perm_node.py | 18 +++++++++++++----- apps/perms/utils/user_perm.py | 1 + 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index 204859f36..e67020962 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -96,16 +96,17 @@ class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): """ 用户授权的节点的子节点与资产树 """ def get_nodes_assets(self): - nodes = PermNode.objects.none() - assets = Asset.objects.none() query_node_util = UserPermNodeUtil(self.user) query_asset_util = UserPermAssetUtil(self.user) node_key = self.query_node_key if not node_key: nodes = query_node_util.get_top_level_nodes() + assets = Asset.objects.none() elif node_key == PermNode.UNGROUPED_NODE_KEY: + nodes = PermNode.objects.none() assets = query_asset_util.get_ungroup_assets() elif node_key == PermNode.FAVORITE_NODE_KEY: + nodes = PermNode.objects.none() assets = query_asset_util.get_favorite_assets() else: nodes = query_node_util.get_node_children(node_key) diff --git a/apps/perms/models/perm_node.py b/apps/perms/models/perm_node.py index ed4b4b213..438cd5b2d 100644 --- a/apps/perms/models/perm_node.py +++ b/apps/perms/models/perm_node.py @@ -16,13 +16,18 @@ class NodeFrom(TextChoices): class UserAssetGrantedTreeNodeRelation(FamilyMixin, JMSOrgBaseModel): NodeFrom = NodeFrom - id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name=_('ID')) + id = models.AutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name=_('ID') + ) user = models.ForeignKey('users.User', db_constraint=False, on_delete=models.CASCADE) - node = models.ForeignKey('assets.Node', default=None, on_delete=models.CASCADE, - db_constraint=False, null=False, related_name='granted_node_rels') + node = models.ForeignKey( + 'assets.Node', default=None, on_delete=models.CASCADE, db_constraint=False, null=False, + related_name='granted_node_rels' + ) node_key = models.CharField(max_length=64, verbose_name=_("Key"), db_index=True) - node_parent_key = models.CharField(max_length=64, default='', verbose_name=_('Parent key'), - db_index=True) + node_parent_key = models.CharField( + max_length=64, default='', verbose_name=_('Parent key'), db_index=True + ) node_from = models.CharField(choices=NodeFrom.choices, max_length=16, db_index=True) node_assets_amount = models.IntegerField(default=0) comment = '' @@ -68,6 +73,9 @@ class PermNode(Node): 'node_from': F('granted_node_rels__node_from') } + def __str__(self): + return f'{self.name}' + def use_granted_assets_amount(self): self.assets_amount = self.granted_assets_amount diff --git a/apps/perms/utils/user_perm.py b/apps/perms/utils/user_perm.py index a5f956de8..ba7f37855 100644 --- a/apps/perms/utils/user_perm.py +++ b/apps/perms/utils/user_perm.py @@ -151,6 +151,7 @@ class UserPermNodeUtil: def get_top_level_nodes(self): nodes = self.get_special_nodes() real_nodes = self._get_indirect_perm_node_children(key='') + nodes.extend(real_nodes) if len(real_nodes) == 1: children = self.get_node_children(real_nodes[0].key) nodes.extend(children) From e347e05210ea8dc12226271514447fa69f7d8787 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 21 Dec 2022 18:26:06 +0800 Subject: [PATCH 083/132] perf: terminal type remove ssh --- apps/terminal/connect_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/connect_methods.py b/apps/terminal/connect_methods.py index 10bf30c81..454abb522 100644 --- a/apps/terminal/connect_methods.py +++ b/apps/terminal/connect_methods.py @@ -153,7 +153,7 @@ class ConnectMethodUtil: protocols = { TerminalType.koko: { 'web_methods': [WebMethod.web_cli, WebMethod.web_sftp], - 'listen': [Protocol.ssh, Protocol.http], + 'listen': [Protocol.http], 'support': [ Protocol.ssh, Protocol.telnet, Protocol.mysql, Protocol.postgresql, From 17627390f8ea7c11c8262baf94b6cbdb72e4944a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Dec 2022 18:36:15 +0800 Subject: [PATCH 084/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20account=20?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8F=B7=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/account.py | 21 +++++---- apps/assets/serializers/account/account.py | 53 +++++++++++++--------- apps/locale/zh/LC_MESSAGES/django.po | 2 +- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index ebcfb1f09..9e2697adb 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -1,20 +1,23 @@ from django.shortcuts import get_object_or_404 from rest_framework.decorators import action -from rest_framework.response import Response from rest_framework.generics import CreateAPIView, ListAPIView +from rest_framework.response import Response -from orgs.mixins.api import OrgBulkModelViewSet - -from common.mixins import RecordViewLogMixin -from assets.models import Account, Asset -from assets.filters import AccountFilterSet -from assets.tasks import verify_accounts_connectivity from assets import serializers +from assets.filters import AccountFilterSet +from assets.models import Account, Asset +from assets.tasks import verify_accounts_connectivity +from authentication.const import ConfirmType +from common.mixins import RecordViewLogMixin +from common.permissions import UserConfirmation +from orgs.mixins.api import OrgBulkModelViewSet __all__ = [ 'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI', 'AccountHistoriesSecretAPI' ] +from rbac.permissions import RBACPermission + class AccountViewSet(OrgBulkModelViewSet): model = Account @@ -62,8 +65,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): 'default': serializers.AccountSecretSerializer, } http_method_names = ['get', 'options'] - # Todo: 记得打开 - # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] + permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] rbac_perms = { 'list': 'assets.view_accountsecret', 'retrieve': 'assets.view_accountsecret', @@ -110,4 +112,5 @@ class AccountTaskCreateAPI(CreateAPIView): def get_exception_handler(self): def handler(e, context): return Response({"error": str(e)}, status=400) + return handler diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index f8186d13e..218155b66 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -1,15 +1,15 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.drf.serializers import SecretReadableMixin -from common.drf.fields import ObjectRelatedField, LabeledChoiceField -from assets.tasks import push_accounts_to_assets -from assets.models import Account, AccountTemplate, Asset -from .base import BaseAccountSerializer from assets.const import SecretType +from assets.models import Account, AccountTemplate, Asset +from assets.tasks import push_accounts_to_assets +from common.drf.fields import ObjectRelatedField, LabeledChoiceField +from common.drf.serializers import SecretReadableMixin, BulkModelSerializer +from .base import BaseAccountSerializer -class AccountSerializerCreateMixin(serializers.ModelSerializer): +class AccountSerializerCreateMixin(BulkModelSerializer): template = serializers.UUIDField( required=False, allow_null=True, write_only=True, label=_('Account template') @@ -53,11 +53,27 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): return instance +class AccountAssetSerializer(serializers.ModelSerializer): + platform = ObjectRelatedField(read_only=True) + + class Meta: + model = Asset + fields = ['id', 'name', 'address', 'platform'] + + def to_internal_value(self, data): + if isinstance(data, dict): + i = data.get('id') + else: + i = data + + try: + return Asset.objects.get(id=i) + except Asset.DoesNotExist: + raise serializers.ValidationError(_('Asset not found')) + + class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): - asset = ObjectRelatedField( - required=False, queryset=Asset.objects, - label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id') - ) + asset = AccountAssetSerializer(label=_('Asset')) su_from = ObjectRelatedField( required=False, queryset=Account.objects, allow_null=True, allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username') @@ -66,22 +82,17 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): class Meta(BaseAccountSerializer.Meta): model = Account fields = BaseAccountSerializer.Meta.fields \ - + ['su_from', 'version', 'asset'] \ - + ['template', 'push_now'] + + ['su_from', 'version', 'asset'] \ + + ['template', 'push_now'] extra_kwargs = { **BaseAccountSerializer.Meta.extra_kwargs, 'name': {'required': False, 'allow_null': True}, } - def __init__(self, *args, data=None, **kwargs): - super().__init__(*args, data=data, **kwargs) - if data and 'name' not in data: - username = data.get('username') - if username is not None: - data['name'] = username - if hasattr(self, 'initial_data') and \ - not getattr(self, 'initial_data', None): - delattr(self, 'initial_data') + def validate_name(self, value): + if not value: + value = self.initial_data.get('username') + return value @classmethod def setup_eager_loading(cls, queryset): diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 256794ea4..72c101a71 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -872,7 +872,7 @@ msgstr "自动化任务执行历史" #: assets/models/automations/change_secret.py:15 assets/models/base.py:67 #: assets/serializers/account/account.py:97 assets/serializers/base.py:13 msgid "Secret type" -msgstr "密问类型" +msgstr "密文类型" #: assets/models/automations/change_secret.py:19 #: assets/serializers/automations/change_secret.py:25 From b01ef1585c1d0d92c1e75f0edc0cad3ee6022672 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Dec 2022 18:37:05 +0800 Subject: [PATCH 085/132] perf: account secret rbac permission --- apps/assets/api/account/account.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 9e2697adb..8dad13b3c 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -76,8 +76,7 @@ class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView): model = Account.history.model serializer_class = serializers.AccountHistorySerializer http_method_names = ['get', 'options'] - # Todo: 记得打开 - # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] + permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] rbac_perms = { 'list': 'assets.view_accountsecret', } From 0c2a5bc44a53f1db530d3c9ebb7e6daedf98eea7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Dec 2022 19:59:56 +0800 Subject: [PATCH 086/132] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E8=B5=84=E4=BA=A7=E5=B9=B3=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 3b32ca748..bb2921717 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -36,7 +36,7 @@ class AssetFilterSet(BaseFilterSet): model = Asset fields = [ "id", "name", "address", "is_active", - "type", "category" + "type", "category", "platform" ] From 7ca2fdca89a6a2e0afb61b3134321ac95892fa0f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Dec 2022 20:04:49 +0800 Subject: [PATCH 087/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platform?= =?UTF-8?q?=20=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index bb2921717..cd4166390 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -31,6 +31,7 @@ __all__ = [ class AssetFilterSet(BaseFilterSet): type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact") category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact") + platform = django_filters.CharFilter(method='filter_platform') class Meta: model = Asset @@ -39,6 +40,13 @@ class AssetFilterSet(BaseFilterSet): "type", "category", "platform" ] + @staticmethod + def filter_platform(queryset, name, value): + if value.isdigit(): + return queryset.filter(platform_id=value) + else: + return queryset.filter(platform__name=value) + class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): """ From f4b0ba43a2eae8c308ed6ad6d5cd52c49c812be4 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 21 Dec 2022 20:19:13 +0800 Subject: [PATCH 088/132] perf: filter application --- apps/rbac/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/rbac/const.py b/apps/rbac/const.py index 541b0b3da..dc9d2592b 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -94,6 +94,7 @@ exclude_permissions = ( ('terminal', 'sessionsharing', 'view,add,change,delete', 'sessionsharing'), ('terminal', 'session', 'delete,share', 'session'), ('terminal', 'session', 'delete,change', 'command'), + ('applications', '*', '*', '*'), ) From 5d865ffd5433ad1e93de50eeeffe87702471b9e8 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 21 Dec 2022 18:38:29 +0800 Subject: [PATCH 089/132] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9db=5Fport=5Fma?= =?UTF-8?q?pper=E7=AD=96=E7=95=A5;=20=E5=90=AF=E5=8A=A8=E6=97=B6=E8=BF=9B?= =?UTF-8?q?=E8=A1=8Ccheck=E6=A0=A1=E9=AA=8C;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/signal_handlers.py | 2 +- apps/terminal/utils/db_port_mapper.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index d98e21eee..ec7eed45f 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -45,7 +45,7 @@ def on_applet_create(sender, instance, created=False, **kwargs): def init_db_port_mapper(sender, **kwargs): logger.info('Init db port mapper') try: - db_port_manager.init() + db_port_manager.check() except (ProgrammingError,) as e: pass diff --git a/apps/terminal/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py index d343dd3cb..4ca8cebec 100644 --- a/apps/terminal/utils/db_port_mapper.py +++ b/apps/terminal/utils/db_port_mapper.py @@ -34,9 +34,22 @@ class DBPortManager(object): def magnus_listen_port_range(self): return settings.MAGNUS_PORTS - def init(self): + @staticmethod + def fetch_dbs(): with tmp_to_root_org(): - db_ids = Asset.objects.filter(platform__category=Category.DATABASE).values_list('id', flat=True) + dbs = Asset.objects.filter(platform__category=Category.DATABASE).order_by('id') + return dbs + + def check(self): + dbs = self.fetch_dbs() + for db in dbs: + port = self.get_port_by_db(db, raise_exception=False) + if not port: + self.add(db) + + def init(self): + dbs = self.fetch_dbs() + db_ids = dbs.values_list('id', flat=True) db_ids = [str(i) for i in db_ids] mapper = dict(zip(self.all_available_ports, list(db_ids))) self.set_mapper(mapper) From 00c955e8c0eb2708b6806c8a475ba59e20a46dd3 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 21 Dec 2022 21:07:25 +0800 Subject: [PATCH 090/132] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=90=8D=20check=5Fdb=5Fport=5Fmapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/signal_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index ec7eed45f..046c1b122 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -42,7 +42,7 @@ def on_applet_create(sender, instance, created=False, **kwargs): @receiver(django_ready) -def init_db_port_mapper(sender, **kwargs): +def check_db_port_mapper(sender, **kwargs): logger.info('Init db port mapper') try: db_port_manager.check() From e0e57a71aaad83590888efc188c8fd529ff4e0a5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 22 Dec 2022 11:34:18 +0800 Subject: [PATCH 091/132] =?UTF-8?q?pref:=20=E7=B1=BB=E5=9E=8B=E6=A0=91?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=B5=84=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/tree.py | 68 +++++++++++++----------------------- apps/assets/const/types.py | 38 +++++++++++--------- apps/assets/models/node.py | 6 ++++ apps/assets/urls/api_urls.py | 2 +- 4 files changed, 52 insertions(+), 62 deletions(-) diff --git a/apps/assets/api/tree.py b/apps/assets/api/tree.py index a635fef0b..a248fe042 100644 --- a/apps/assets/api/tree.py +++ b/apps/assets/api/tree.py @@ -1,6 +1,5 @@ # ~*~ coding: utf-8 ~*~ -from django.core.exceptions import PermissionDenied from rest_framework.generics import get_object_or_404 from rest_framework.response import Response @@ -12,44 +11,20 @@ from orgs.utils import current_org from .mixin import SerializeToTreeNodeMixin from .. import serializers from ..const import AllTypes -from ..models import Node +from ..models import Node, Platform, Asset logger = get_logger(__file__) __all__ = [ 'NodeChildrenApi', - 'NodeListAsTreeApi', 'NodeChildrenAsTreeApi', 'CategoryTreeApi', ] -class NodeListAsTreeApi(generics.ListAPIView): - """ - 获取节点列表树 - [ - { - "id": "", - "name": "", - "pId": "", - "meta": "" - } - ] - """ - model = Node - serializer_class = TreeNodeSerializer - - @staticmethod - def to_tree_queryset(queryset): - queryset = [node.as_tree_node() for node in queryset] - return queryset - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.to_tree_queryset(queryset) - return queryset - - class NodeChildrenApi(generics.ListCreateAPIView): + """ + 节点的增删改查 + """ serializer_class = serializers.NodeSerializer search_fields = ('value',) @@ -141,33 +116,38 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): def list(self, request, *args, **kwargs): nodes = self.filter_queryset(self.get_queryset()).order_by('value') nodes = self.serialize_nodes(nodes, with_asset_amount=True) - assets = self.get_assets() + assets = self.get_assets_as_node() data = [*nodes, *assets] return Response(data=data) - def get_assets(self): + def get_assets_as_node(self): include_assets = self.request.query_params.get('assets', '0') == '1' if not self.instance or not include_assets: return [] - assets = self.instance.get_assets().only( - "id", "name", "address", "platform_id", - "org_id", "is_active", - ).prefetch_related('platform') + assets = self.instance.get_assets_for_tree() return self.serialize_assets(assets, self.instance.key) class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView): serializer_class = TreeNodeSerializer + rbac_perms = { + 'GET': 'assets.view_asset', + 'list': 'assets.view_asset', + } - def check_permissions(self, request): - if not request.user.has_perm('assets.view_asset'): - raise PermissionDenied - return True + def get_assets(self): + key = self.request.query_params.get('key') + platform = Platform.objects.filter(id=key).first() + if not platform: + return [] + assets = Asset.objects.filter(platform=platform).prefetch_related('platform') + return self.serialize_assets(assets, key) def list(self, request, *args, **kwargs): - if request.query_params.get('key'): - nodes = [] + include_asset = bool(self.request.query_params.get('assets')) + + if include_asset and self.request.query_params.get('key'): + nodes = self.get_assets() else: - nodes = AllTypes.to_tree_nodes() - serializer = self.get_serializer(nodes, many=True) - return Response(data=serializer.data) + nodes = AllTypes.to_tree_nodes(include_asset) + return Response(data=nodes) diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 6a0881e5d..1bf61a583 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -1,8 +1,9 @@ from collections import defaultdict from copy import deepcopy +from django.utils.translation import gettext as _ + from common.db.models import ChoicesMixin -from common.tree import TreeNode from .category import Category from .cloud import CloudTypes from .database import DatabaseTypes @@ -134,34 +135,34 @@ class AllTypes(ChoicesMixin): @staticmethod def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): - node = TreeNode(**{ + node = { 'id': pid + '_' + choice.name, 'name': choice.label, 'title': choice.label, 'pId': pid, 'open': opened, 'isParent': is_parent, - }) + } if meta: - node.meta = meta + node['meta'] = meta return node @classmethod - def platform_to_node(cls, p, pid): - node = TreeNode(**{ + def platform_to_node(cls, p, pid, include_asset): + node = { 'id': '{}'.format(p.id), 'name': p.name, 'title': p.name, 'pId': pid, - 'isParent': True, + 'isParent': include_asset, 'meta': { 'type': 'platform' } - }) + } return node @classmethod - def to_tree_nodes(cls): + def to_tree_nodes(cls, include_asset): from ..models import Asset, Platform asset_platforms = Asset.objects.all().values_list('platform_id', flat=True) platform_count = defaultdict(int) @@ -177,26 +178,29 @@ class AllTypes(ChoicesMixin): category_type_mapper[p.category] += platform_count[p.id] tp_platforms[p.category + '_' + p.type].append(p) - root = TreeNode(id='ROOT', name='所有类型', title='所有类型', open=True, isParent=True) + root = dict(id='ROOT', name=_('All types'), title='所有类型', open=True, isParent=True) nodes = [root] for category, type_cls in cls.category_types(): + # Category 格式化 meta = {'type': 'category', 'category': category.value} category_node = cls.choice_to_node(category, 'ROOT', meta=meta) category_count = category_type_mapper.get(category, 0) - category_node.name += f'({category_count})' + category_node['name'] += f'({category_count})' nodes.append(category_node) - tps = type_cls.get_types() - for tp in tps: + # Type 格式化 + types = type_cls.get_types() + for tp in types: meta = {'type': 'type', 'category': category.value, '_type': tp.value} - tp_node = cls.choice_to_node(tp, category_node.id, opened=False, meta=meta) + tp_node = cls.choice_to_node(tp, category_node['id'], opened=False, meta=meta) tp_count = category_type_mapper.get(category + '_' + tp, 0) - tp_node.name += f'({tp_count})' + tp_node['name'] += f'({tp_count})' nodes.append(tp_node) + # Platform 格式化 for p in tp_platforms.get(category + '_' + tp, []): - platform_node = cls.platform_to_node(p, tp_node.id) - platform_node.name += f'({platform_count.get(p.id, 0)})' + platform_node = cls.platform_to_node(p, tp_node['id'], include_asset) + platform_node['name'] += f'({platform_count.get(p.id, 0)})' nodes.append(platform_node) return nodes diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 5d1af0ec5..626f931d0 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -430,6 +430,12 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin): assets = Asset.objects.filter(nodes=self) return assets.distinct() + def get_assets_for_tree(self): + return self.get_assets().only( + "id", "name", "address", "platform_id", + "org_id", "is_active" + ).prefetch_related('platform') + def get_valid_assets(self): return self.get_assets().valid() diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 773f5c348..2f8033d30 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -50,7 +50,7 @@ urlpatterns = [ name='account-secret-history'), path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'), - path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), + # path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'), path('nodes//children/', api.NodeChildrenApi.as_view(), name='node-children'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), From c39e2e9a8aebb11900336ea782380a9df45d2954 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, 22 Dec 2022 12:47:46 +0800 Subject: [PATCH 092/132] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20procps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 + Dockerfile.loong64 | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 37f573be9..cf8f73a29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,7 @@ ARG TOOLS=" \ default-mysql-client \ locales \ openssh-client \ + procps \ sshpass \ telnet \ unzip \ diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 5e942d5a3..f1abd0fe6 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -36,6 +36,7 @@ ARG TOOLS=" \ default-mysql-client \ locales \ openssh-client \ + procps \ sshpass \ telnet \ unzip \ From 1de51a2bfd1b1fb3fc22e45dd58088de3d8eec03 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 22 Dec 2022 13:50:22 +0800 Subject: [PATCH 093/132] perf: asset xpack (#9234) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/const/cloud.py | 4 +++- apps/assets/const/device.py | 4 +++- apps/assets/const/web.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index 1e16c9b13..be2637ddf 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -52,4 +52,6 @@ class CloudTypes(BaseType): @classmethod def get_community_types(cls): - return [cls.K8S] + return [ + cls.K8S, cls.PUBLIC, cls.PRIVATE + ] diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index 45cfb05e7..2508900dc 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -55,4 +55,6 @@ class DeviceTypes(BaseType): @classmethod def get_community_types(cls): - return [] + return [ + cls.GENERAL, cls.SWITCH, cls.ROUTER, cls.FIREWALL + ] diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py index 061030ab9..88ff7f8f9 100644 --- a/apps/assets/const/web.py +++ b/apps/assets/const/web.py @@ -47,4 +47,6 @@ class WebTypes(BaseType): @classmethod def get_community_types(cls): - return [] + return [ + cls.WEBSITE, + ] From 2e91aa8ce555f00e3ae9dd51504691e310f5dab4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 22 Dec 2022 13:58:29 +0800 Subject: [PATCH 094/132] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20celery=20?= =?UTF-8?q?=E5=81=A5=E5=BA=B7=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/celery/__init__.py | 4 +++- apps/ops/celery/heatbeat.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 apps/ops/celery/heatbeat.py diff --git a/apps/ops/celery/__init__.py b/apps/ops/celery/__init__.py index cb7bdcb88..6419fee9c 100644 --- a/apps/ops/celery/__init__.py +++ b/apps/ops/celery/__init__.py @@ -2,12 +2,14 @@ import os -from kombu import Exchange, Queue from celery import Celery +from kombu import Exchange, Queue # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jumpserver.settings') from jumpserver import settings +from .heatbeat import * + # from django.conf import settings app = Celery('jumpserver') diff --git a/apps/ops/celery/heatbeat.py b/apps/ops/celery/heatbeat.py new file mode 100644 index 000000000..339a3c60a --- /dev/null +++ b/apps/ops/celery/heatbeat.py @@ -0,0 +1,25 @@ +from pathlib import Path + +from celery.signals import heartbeat_sent, worker_ready, worker_shutdown + + +@heartbeat_sent.connect +def heartbeat(sender, **kwargs): + worker_name = sender.eventer.hostname.split('@')[0] + heartbeat_path = Path('/tmp/worker_heartbeat_{}'.format(worker_name)) + heartbeat_path.touch() + + +@worker_ready.connect +def worker_ready(sender, **kwargs): + worker_name = sender.hostname.split('@')[0] + ready_path = Path('/tmp/worker_ready_{}'.format(worker_name)) + ready_path.touch() + + +@worker_shutdown.connect +def worker_shutdown(sender, **kwargs): + worker_name = sender.hostname.split('@')[0] + for signal in ['ready', 'heartbeat']: + path = Path('/tmp/worker_{}_{}'.format(signal, worker_name)) + path.unlink(missing_ok=True) From fd323c20e179a0e616acaf226d42565bf1b5f2eb 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, 22 Dec 2022 14:27:13 +0800 Subject: [PATCH 095/132] =?UTF-8?q?perf:=20=E6=9B=B4=E6=96=B0=20celery=20?= =?UTF-8?q?=E5=81=A5=E5=BA=B7=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/check_celery.sh | 11 ++++++----- utils/upgrade.sh | 45 ------------------------------------------- 2 files changed, 6 insertions(+), 50 deletions(-) delete mode 100644 utils/upgrade.sh diff --git a/utils/check_celery.sh b/utils/check_celery.sh index 4673e0918..ba2a8777a 100644 --- a/utils/check_celery.sh +++ b/utils/check_celery.sh @@ -1,7 +1,8 @@ #!/bin/bash -if [[ "$(ps axu | grep 'celery' | grep -v 'grep' | grep -cv 'defunct')" -gt "2" ]];then - exit 0 -else - exit 1 -fi \ No newline at end of file +set -e + +test -e /tmp/worker_ready_ansible +test -e /tmp/worker_ready_celery +test -e /tmp/worker_heartbeat_ansible && test $(($(date +%s) - $(stat -c %Y /tmp/worker_heartbeat_ansible))) -lt 10 +test -e /tmp/worker_heartbeat_celery && test $(($(date +%s) - $(stat -c %Y /tmp/worker_heartbeat_celery))) -lt 10 diff --git a/utils/upgrade.sh b/utils/upgrade.sh deleted file mode 100644 index f700c9f7d..000000000 --- a/utils/upgrade.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -if grep -q 'source /opt/autoenv/activate.sh' ~/.bashrc; then - echo -e "\033[31m 正在自动载入 python 环境 \033[0m" -else - echo -e "\033[31m 不支持自动升级,请参考 http://docs.jumpserver.org/zh/docs/upgrade.html 手动升级 \033[0m" - exit 0 -fi - -source ~/.bashrc - -cd `dirname $0`/ && cd .. && ./jms stop - -jumpserver_backup=/tmp/jumpserver_backup$(date -d "today" +"%Y%m%d_%H%M%S") -mkdir -p $jumpserver_backup -cp -r ./* $jumpserver_backup - -echo -e "\033[31m 是否需要备份Jumpserver数据库 \033[0m" -stty erase ^H -read -p "确认备份请按Y,否则按其他键跳过备份 " a -if [ "$a" == y -o "$a" == Y ];then - echo -e "\033[31m 正在备份数据库 \033[0m" - echo -e "\033[31m 请手动输入数据库信息 \033[0m" - read -p '请输入Jumpserver数据库ip:' DB_HOST - read -p '请输入Jumpserver数据库端口:' DB_PORT - read -p '请输入Jumpserver数据库名称:' DB_NAME - read -p '请输入有权限导出数据库的用户:' DB_USER - read -p '请输入该用户的密码:' DB_PASSWORD - mysqldump -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD $DB_NAME > /$jumpserver_backup/$DB_NAME$(date -d "today" +"%Y%m%d_%H%M%S").sql || { - echo -e "\033[31m 备份数据库失败,请检查输入是否有误 \033[0m" - exit 1 - } - echo -e "\033[31m 备份数据库完成 \033[0m" -else - echo -e "\033[31m 已取消备份数据库操作 \033[0m" -fi - -git pull && pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh - -cd .. && ./jms start all -d -echo -e "\033[31m 请检查jumpserver是否启动成功 \033[0m" -echo -e "\033[31m 备份文件存放于$jumpserver_backup目录 \033[0m" -stty erase ^? - -exit 0 From 65387ebff44112b5f52f8536d5ed30cb0b6dd6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chuailei000=E2=80=9D?= <2280131253@qq.com> Date: Thu, 22 Dec 2022 15:42:45 +0800 Subject: [PATCH 096/132] =?UTF-8?q?perf:=20=E8=B0=83=E6=95=B4=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=A1=B5=E9=9D=A2=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/templates/authentication/login.html | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 2cc0d2781..ad01c12cd 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -70,7 +70,7 @@ .right-image-box { height: 100%; - width: 56%; + width: 50%; float: right; } @@ -78,7 +78,7 @@ text-align: center; background-color: white; height: 100%; - width: 44%; + width: 50%; border-right: 1px solid #EFF0F1; } @@ -204,6 +204,9 @@ .auto-login input[type=checkbox]:checked { border: 4px solid var(--primary-color); } + .auto-login > .row::after { + clear: none; + } @@ -274,7 +277,7 @@
{% endif %}