Compare commits

...

35 Commits

Author SHA1 Message Date
jiangweidong
ecdc1a056a feat: Support automations for website, including ping, secret change, and account verification 2025-09-05 17:39:13 +08:00
jiangweidong
5001ca6960 feat: Support automations for website, including ping, secret change, and account verification 2025-09-05 17:37:49 +08:00
fit2bot
16461b0fa9 perf: support global search (#15961)
* perf: support global search

* perf: change serach

* perf: search model add asset permission

---------

Co-authored-by: mikebofs <mikebofs@gmail.com>
Co-authored-by: ibuler <ibuler@qq.com>
2025-09-05 16:40:18 +08:00
mikebofs
528b0ea1ba perf: change some api view default limit 2025-09-05 16:20:26 +08:00
ibuler
60f06adaa9 fix: wechat or phone decrypt err 2025-09-04 11:59:04 +08:00
Bai
7a6187b95f fix: temp token backend 2025-09-03 18:10:10 +08:00
Bai
aacaf3a174 perf: aks encrypt 2025-09-03 11:16:04 +08:00
Bai
3c9d2534fa perf: aks encrypt 2025-09-03 11:16:04 +08:00
wangruidong
4f79abe678 perf: Connect methods acl allow accept action 2025-09-03 11:00:56 +08:00
fit2bot
ae9956ff91 chore: change readme 2025-09-02 15:22:44 +08:00
Bai
429677e0ce perf: readme 2025-09-02 14:54:28 +08:00
ibuler
034ee65157 perf: decrypt secret logic 2025-09-02 10:38:10 +08:00
Eric
fdd7d9b6b1 perf: add vnc client method 2025-09-02 10:34:39 +08:00
wangruidong
db0e21f5d9 fix: Lazy import Azure and Google Cloud dependencies 2025-08-29 11:10:43 +08:00
wangruidong
468b84eb3d perf: Validate connection token id 2025-08-29 11:09:40 +08:00
ibuler
28d5475d0f perf: try to decrypt then origin value 2025-08-29 11:00:02 +08:00
ibuler
b9c60d856f perf: allow some api page no limits 2025-08-28 17:05:11 +08:00
feng
bd1d73c6dd perf: Report localtime 2025-08-28 15:39:54 +08:00
wangruidong
bf92c756d4 fix: Ensure command arguments are safely quoted in safe_run_cmd 2025-08-28 14:14:55 +08:00
feng
62ebe0d636 perf: Third login redirect url query string 2025-08-27 14:45:56 +08:00
github-actions[bot]
0b1fea8492 perf: Update Dockerfile with new base image tag 2025-08-27 11:05:19 +08:00
mikebofs
65b5f573f8 perf: change requirements 2025-08-27 11:05:19 +08:00
mikebofs
bb639e1fe7 perf: revert django-simple-history version 2025-08-27 10:43:21 +08:00
fit2bot
395b868dcf perf: swagger done (#15865)
* perf: swagger upgrade

* perf: upgrade to drf-spectacular

* perf: 添加部分注解

* perf: swagger done

---------

Co-authored-by: ibuler <ibuler@qq.com>
2025-08-27 10:27:01 +08:00
wangruidong
1350b774b3 perf: Improve chart rendering wait logic in export process 2025-08-26 16:20:22 +08:00
wrd
af7a00c1b1 fix: typo 2025-08-26 15:31:13 +08:00
wangruidong
965ec7007c perf: Enhance eager loading by including labels in queryset 2025-08-26 15:31:13 +08:00
fit2bot
1372fd7535 feat: asset permission support exclude some account
* perf: add perm exclude

* perf: exclude node action account

* perf: add i18n

* perf: pop exclude account

---------

Co-authored-by: mikebofs <mikebofs@gmail.com>
2025-08-26 14:57:57 +08:00
wangruidong
3b0ef4cca7 fix: Add nmap to Dockerfile dependencies 2025-08-25 16:29:10 +08:00
Aaron3S
6832abdaad feat: change some translate 2025-08-25 11:05:49 +08:00
feng
c6bf290dbb perf: Report translate 2025-08-22 18:57:14 +08:00
feng
23ab66c11a perf: Translate 2025-08-22 18:05:30 +08:00
feng
1debaa5547 perf: report perm 2025-08-22 17:53:52 +08:00
Bai
47413966c9 perf: captcha > CAPTCHA 2025-08-22 16:25:45 +08:00
Eric
703f39607c perf: default allow hosts 2025-08-22 14:12:45 +08:00
151 changed files with 7908 additions and 955 deletions

View File

@@ -1,4 +1,4 @@
FROM jumpserver/core-base:20250819_064003 AS stage-build
FROM jumpserver/core-base:20250827_025554 AS stage-build
ARG VERSION
@@ -33,6 +33,7 @@ ARG TOOLS=" \
default-libmysqlclient-dev \
openssh-client \
sshpass \
nmap \
bubblewrap"
ARG APT_MIRROR=http://deb.debian.org

View File

@@ -2,7 +2,7 @@
<a name="readme-top"></a>
<a href="https://jumpserver.com" target="_blank"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
## An open-source PAM tool (Bastion Host)
## An open-source PAM platform (Bastion Host)
[![][license-shield]][license-link]
[![][docs-shield]][docs-link]
@@ -19,7 +19,7 @@
## What is JumpServer?
JumpServer is an open-source Privileged Access Management (PAM) tool that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.
JumpServer is an open-source Privileged Access Management (PAM) platform that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.
<picture>

View File

@@ -190,6 +190,7 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixi
rbac_perms = {
'GET': 'accounts.view_accountsecret',
}
queryset = Account.history.model.objects.none()
@lazyproperty
def account(self) -> Account:

View File

@@ -12,6 +12,8 @@ class VirtualAccountViewSet(OrgBulkModelViewSet):
filterset_fields = ('alias',)
def get_queryset(self):
if getattr(self, "swagger_fake_view", False):
return VirtualAccount.objects.none()
return VirtualAccount.get_or_init_queryset()
def get_object(self, ):

View File

@@ -41,6 +41,7 @@ class AutomationAssetsListApi(generics.ListAPIView):
class AutomationRemoveAssetApi(generics.UpdateAPIView):
model = BaseAutomation
queryset = BaseAutomation.objects.all()
serializer_class = serializers.UpdateAssetSerializer
http_method_names = ['patch']
@@ -59,6 +60,7 @@ class AutomationRemoveAssetApi(generics.UpdateAPIView):
class AutomationAddAssetApi(generics.UpdateAPIView):
model = BaseAutomation
queryset = BaseAutomation.objects.all()
serializer_class = serializers.UpdateAssetSerializer
http_method_names = ['patch']

View File

@@ -154,12 +154,10 @@ class ChangSecretAddAssetApi(AutomationAddAssetApi):
model = ChangeSecretAutomation
serializer_class = serializers.ChangeSecretUpdateAssetSerializer
class ChangSecretNodeAddRemoveApi(AutomationNodeAddRemoveApi):
model = ChangeSecretAutomation
serializer_class = serializers.ChangeSecretUpdateNodeSerializer
class ChangeSecretStatusViewSet(OrgBulkModelViewSet):
perm_model = ChangeSecretAutomation
filterset_class = ChangeSecretStatusFilterSet

View File

@@ -62,7 +62,8 @@ class ChangeSecretDashboardApi(APIView):
status_counts = defaultdict(lambda: defaultdict(int))
for date_finished, status in results:
date_str = str(date_finished.date())
dt_local = timezone.localtime(date_finished)
date_str = str(dt_local.date())
if status == ChangeSecretRecordStatusChoice.failed:
status_counts[date_str]['failed'] += 1
elif status == ChangeSecretRecordStatusChoice.success:

View File

@@ -150,6 +150,9 @@ class CheckAccountEngineViewSet(JMSModelViewSet):
http_method_names = ['get', 'options']
def get_queryset(self):
if getattr(self, "swagger_fake_view", False):
return CheckAccountEngine.objects.none()
return CheckAccountEngine.get_default_engines()
def filter_queryset(self, queryset: list):

View File

@@ -63,12 +63,10 @@ class PushAccountRemoveAssetApi(AutomationRemoveAssetApi):
model = PushAccountAutomation
serializer_class = serializers.PushAccountUpdateAssetSerializer
class PushAccountAddAssetApi(AutomationAddAssetApi):
model = PushAccountAutomation
serializer_class = serializers.PushAccountUpdateAssetSerializer
class PushAccountNodeAddRemoveApi(AutomationNodeAddRemoveApi):
model = PushAccountAutomation
serializer_class = serializers.PushAccountUpdateNodeSerializer
serializer_class = serializers.PushAccountUpdateNodeSerializer

View File

@@ -105,6 +105,10 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
h['account']['mode'] = 'sysdba' if account.privileged else None
return h
def add_extra_params(self, host, **kwargs):
host['ssh_params'] = {}
return host
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super().host_callback(
host, asset=asset, account=account, automation=automation,
@@ -113,8 +117,7 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
if host.get('error'):
return host
host['ssh_params'] = {}
host = self.add_extra_params(host, automation=automation)
accounts = self.get_accounts(account)
existing_ids = set(map(str, accounts.values_list('id', flat=True)))
missing_ids = set(map(str, self.account_ids)) - existing_ids

View File

@@ -5,6 +5,9 @@ from django.conf import settings
from django.utils.translation import gettext_lazy as _
from xlsxwriter import Workbook
from assets.automations.methods import platform_automation_methods as asset_methods
from assets.const import AutomationTypes as AssetAutomationTypes
from accounts.automations.methods import platform_automation_methods as account_methods
from accounts.const import (
AutomationTypes, SecretStrategy, ChangeSecretRecordStatusChoice
)
@@ -22,6 +25,22 @@ logger = get_logger(__name__)
class ChangeSecretManager(BaseChangeSecretPushManager):
ansible_account_prefer = ''
def get_method_id_meta_mapper(self):
return {
method["id"]: method for method in self.platform_automation_methods
}
@property
def platform_automation_methods(self):
return asset_methods + account_methods
def add_extra_params(self, host, **kwargs):
host = super().add_extra_params(host, **kwargs)
automation = kwargs.get('automation')
for extra_type in [AssetAutomationTypes.ping, AutomationTypes.verify_account]:
host[f"{extra_type}_params"] = self.get_params(automation, extra_type)
return host
@classmethod
def method_type(cls):
return AutomationTypes.change_secret

View File

@@ -0,0 +1,36 @@
- hosts: website
gather_facts: no
vars:
ansible_python_interpreter: "{{ local_python_interpreter }}"
tasks:
- name: Test privileged account
website_ping:
login_host: "{{ jms_asset.address }}"
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
steps: "{{ ping_params.steps }}"
load_state: "{{ ping_params.load_state }}"
- name: "Change {{ account.username }} password"
website_user:
login_host: "{{ jms_asset.address }}"
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
steps: "{{ params.steps }}"
load_state: "{{ params.load_state }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
register: change_secret_result
- name: "Verify {{ account.username }} password"
website_ping:
login_host: "{{ jms_asset.address }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
steps: "{{ verify_account_params.steps }}"
load_state: "{{ verify_account_params.load_state }}"
when:
- check_conn_after_change or change_secret_result.failed | default(false)
delegate_to: localhost

View File

@@ -0,0 +1,51 @@
id: change_account_website
name: "{{ 'Website account change secret' | trans }}"
category: web
type:
- website
method: change_secret
priority: 50
params:
- name: load_state
type: choice
label: "{{ 'Load state' | trans }}"
choices:
- [ networkidle, "{{ 'Network idle' | trans }}" ]
- [ domcontentloaded, "{{ 'Dom content loaded' | trans }}" ]
- [ load, "{{ 'Load completed' | trans }}" ]
default: 'load'
- name: steps
type: list
default: [ ]
label: "{{ 'Steps' | trans }}"
help_text: "{{ 'Params step help text' | trans }}"
i18n:
Website account change secret:
zh: 使用 Playwright 模拟浏览器变更账号密码
ja: Playwright を使用してブラウザをシミュレートし、アカウントのパスワードを変更します
en: Use Playwright to simulate a browser for account password change.
Load state:
zh: 加载状态检测
en: Load state detection
ja: ロード状態の検出
Steps:
zh: 步骤
en: Steps
ja: 手順
Network idle:
zh: 网络空闲
en: Network idle
ja: ネットワークが空いた状態
Dom content loaded:
zh: 文档内容加载完成
en: Dom content loaded
ja: ドキュメントの内容がロードされた状態
Load completed:
zh: 全部加载完成
en: All load completed
ja: すべてのロードが完了した状態
Params step help text:
zh: 根据配置决定任务执行步骤
ja: 設定に基づいてタスクの実行ステップを決定する
en: Determine task execution steps based on configuration

View File

@@ -0,0 +1,13 @@
- hosts: website
gather_facts: no
vars:
ansible_python_interpreter: "{{ local_python_interpreter }}"
tasks:
- name: Verify account
website_ping:
login_host: "{{ jms_asset.address }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
steps: "{{ params.steps }}"
load_state: "{{ params.load_state }}"

View File

@@ -0,0 +1,50 @@
id: verify_account_website
name: "{{ 'Website account verify' | trans }}"
category: web
type:
- website
method: verify_account
priority: 50
params:
- name: load_state
type: choice
label: "{{ 'Load state' | trans }}"
choices:
- [ networkidle, "{{ 'Network idle' | trans }}" ]
- [ domcontentloaded, "{{ 'Dom content loaded' | trans }}" ]
- [ load, "{{ 'Load completed' | trans }}" ]
default: 'load'
- name: steps
type: list
label: "{{ 'Steps' | trans }}"
help_text: "{{ 'Params step help text' | trans }}"
default: []
i18n:
Website account verify:
zh: 使用 Playwright 模拟浏览器验证账号
ja: Playwright を使用してブラウザをシミュレートし、アカウントの検証を行います
en: Use Playwright to simulate a browser for account verification.
Load state:
zh: 加载状态检测
en: Load state detection
ja: ロード状態の検出
Steps:
zh: 步骤
en: Steps
ja: 手順
Network idle:
zh: 网络空闲
en: Network idle
ja: ネットワークが空いた状態
Dom content loaded:
zh: 文档内容加载完成
en: Dom content loaded
ja: ドキュメントの内容がロードされた状態
Load completed:
zh: 全部加载完成
en: All load completed
ja: すべてのロードが完了した状態
Params step help text:
zh: 配置步骤,根据配置决定任务执行步骤
ja: パラメータを設定し、設定に基づいてタスクの実行手順を決定します
en: Configure steps, and determine the task execution steps based on the configuration.

View File

@@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
#
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
from azure.identity import ClientSecretCredential
from azure.keyvault.secrets import SecretClient
from common.utils import get_logger
@@ -14,6 +11,9 @@ __all__ = ['AZUREVaultClient']
class AZUREVaultClient(object):
def __init__(self, vault_url, tenant_id, client_id, client_secret):
from azure.identity import ClientSecretCredential
from azure.keyvault.secrets import SecretClient
authentication_endpoint = 'https://login.microsoftonline.com/' \
if ('azure.net' in vault_url) else 'https://login.chinacloudapi.cn/'
@@ -23,6 +23,8 @@ class AZUREVaultClient(object):
self.client = SecretClient(vault_url=vault_url, credential=credentials)
def is_active(self):
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
try:
self.client.set_secret('jumpserver', '666')
except (ResourceNotFoundError, ClientAuthenticationError) as e:
@@ -32,6 +34,8 @@ class AZUREVaultClient(object):
return True, ''
def get(self, name, version=None):
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
try:
secret = self.client.get_secret(name, version)
return secret.value

View File

@@ -132,7 +132,7 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
return self.asset.platform
@lazyproperty
def alias(self):
def alias(self) -> str:
"""
别称,因为有虚拟账号,@INPUT @MANUAL @USER, 否则为 id
"""
@@ -140,13 +140,13 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
return self.username
return str(self.id)
def is_virtual(self):
def is_virtual(self) -> bool:
"""
不要用 username 去判断,因为可能是构造的 account 对象,设置了同名账号的用户名,
"""
return self.alias.startswith('@')
def is_ds_account(self):
def is_ds_account(self) -> bool:
if self.is_virtual():
return ''
if not self.asset.is_directory_service:
@@ -160,7 +160,7 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
return self.asset.ds
@lazyproperty
def ds_domain(self):
def ds_domain(self) -> str:
"""这个不能去掉perm_account 会动态设置这个值,以更改 full_username"""
if self.is_virtual():
return ''
@@ -172,17 +172,17 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
return '@' in self.username or '\\' in self.username
@property
def full_username(self):
def full_username(self) -> str:
if not self.username_has_domain() and self.ds_domain:
return '{}@{}'.format(self.username, self.ds_domain)
return self.username
@lazyproperty
def has_secret(self):
def has_secret(self) -> bool:
return bool(self.secret)
@lazyproperty
def versions(self):
def versions(self) -> int:
return self.history.count()
def get_su_from_accounts(self):

View File

@@ -33,7 +33,7 @@ class IntegrationApplication(JMSOrgBaseModel):
return qs.filter(*query)
@property
def accounts_amount(self):
def accounts_amount(self) -> int:
return self.get_accounts().count()
@property

View File

@@ -75,11 +75,11 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
return bool(self.secret)
@property
def has_username(self):
def has_username(self) -> bool:
return bool(self.username)
@property
def spec_info(self):
def spec_info(self) -> dict:
data = {}
if self.secret_type != SecretType.SSH_KEY:
return data
@@ -87,13 +87,13 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
return data
@property
def password(self):
def password(self) -> str:
if self.secret_type == SecretType.PASSWORD:
return self.secret
return None
@property
def private_key(self):
def private_key(self) -> str:
if self.secret_type == SecretType.SSH_KEY:
return self.secret
return None
@@ -110,7 +110,7 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
return None
@property
def ssh_key_fingerprint(self):
def ssh_key_fingerprint(self) -> str:
if self.public_key:
public_key = self.public_key
elif self.private_key:

View File

@@ -56,7 +56,7 @@ class VaultModelMixin(models.Model):
__secret = None
@property
def secret(self):
def secret(self) -> str:
if self.__secret:
return self.__secret
from accounts.backends import vault_client

View File

@@ -18,11 +18,11 @@ class VirtualAccount(JMSOrgBaseModel):
verbose_name = _('Virtual account')
@property
def name(self):
def name(self) -> str:
return self.get_alias_display()
@property
def username(self):
def username(self) -> str:
usernames_map = {
AliasAccount.INPUT: _("Manual input"),
AliasAccount.USER: _("Same with user"),
@@ -32,7 +32,7 @@ class VirtualAccount(JMSOrgBaseModel):
return usernames_map.get(self.alias, '')
@property
def comment(self):
def comment(self) -> str:
comments_map = {
AliasAccount.INPUT: _('Non-asset account, Input username/password on connect'),
AliasAccount.USER: _('The account username name same with user on connect'),

View File

@@ -456,6 +456,8 @@ class AssetAccountBulkSerializer(
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
spec_info = serializers.DictField(label=_('Spec info'), read_only=True)
class Meta(AccountSerializer.Meta):
fields = AccountSerializer.Meta.fields + ['spec_info']
extra_kwargs = {
@@ -470,6 +472,7 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
class AccountHistorySerializer(serializers.ModelSerializer):
secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type'))
secret = serializers.CharField(label=_('Secret'), read_only=True)
id = serializers.IntegerField(label=_('ID'), source='history_id', read_only=True)
class Meta:

View File

@@ -70,6 +70,8 @@ class AuthValidateMixin(serializers.Serializer):
class BaseAccountSerializer(
AuthValidateMixin, ResourceLabelsMixin, BulkOrgResourceModelSerializer
):
spec_info = serializers.DictField(label=_('Spec info'), read_only=True)
class Meta:
model = BaseAccount
fields_mini = ["id", "name", "username"]

View File

@@ -130,7 +130,7 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer):
read_only_fields = fields
@staticmethod
def get_is_success(obj):
def get_is_success(obj) -> bool:
return obj.status == ChangeSecretRecordStatusChoice.success
@@ -157,7 +157,7 @@ class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
read_only_fields = fields
@staticmethod
def get_asset(instance):
def get_asset(instance) -> str:
return str(instance.asset)
@staticmethod
@@ -165,7 +165,7 @@ class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
return str(instance.account)
@staticmethod
def get_is_success(obj):
def get_is_success(obj) -> str:
if obj.status == ChangeSecretRecordStatusChoice.success.value:
return _("Success")
return _("Failed")
@@ -196,9 +196,9 @@ class ChangeSecretAccountSerializer(serializers.ModelSerializer):
read_only_fields = fields
@staticmethod
def get_meta(obj):
def get_meta(obj) -> dict:
return account_secret_task_status.get(str(obj.id))
@staticmethod
def get_ttl(obj):
def get_ttl(obj) -> int:
return account_secret_task_status.get_ttl(str(obj.id))

View File

@@ -69,7 +69,7 @@ class AssetRiskSerializer(serializers.Serializer):
risk_summary = serializers.SerializerMethodField()
@staticmethod
def get_risk_summary(obj):
def get_risk_summary(obj) -> dict:
summary = {}
for risk in RiskChoice.choices:
summary[f"{risk[0]}_count"] = obj.get(f"{risk[0]}_count", 0)

View File

@@ -1,4 +1,4 @@
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers.mixin import CommonBulkModelSerializer
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
from ..const import ActionChoices
from ..models import ConnectMethodACL
@@ -6,16 +6,15 @@ from ..models import ConnectMethodACL
__all__ = ["ConnectMethodACLSerializer"]
class ConnectMethodACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
class ConnectMethodACLSerializer(BaseSerializer, CommonBulkModelSerializer):
class Meta(BaseSerializer.Meta):
model = ConnectMethodACL
fields = [
i for i in BaseSerializer.Meta.fields + ['connect_methods']
if i not in ['assets', 'accounts']
if i not in ['assets', 'accounts', 'org_id']
]
action_choices_exclude = BaseSerializer.Meta.action_choices_exclude + [
ActionChoices.review,
ActionChoices.accept,
ActionChoices.notice,
ActionChoices.face_verify,
ActionChoices.face_online,

View File

@@ -1,7 +1,7 @@
from django.utils.translation import gettext as _
from common.serializers import CommonBulkModelSerializer
from common.serializers import MethodSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaseUserACLSerializer
from .rules import RuleSerializer
from ..const import ActionChoices
@@ -12,12 +12,12 @@ __all__ = ["LoginACLSerializer"]
common_help_text = _("With * indicating a match all. ")
class LoginACLSerializer(BaseUserACLSerializer, BulkOrgResourceModelSerializer):
class LoginACLSerializer(BaseUserACLSerializer, CommonBulkModelSerializer):
rules = MethodSerializer(label=_('Rule'))
class Meta(BaseUserACLSerializer.Meta):
model = LoginACL
fields = BaseUserACLSerializer.Meta.fields + ['rules', ]
fields = list((set(BaseUserACLSerializer.Meta.fields) | {'rules'}) - {'org_id'})
action_choices_exclude = [
ActionChoices.warning,
ActionChoices.notify_and_warn,

View File

@@ -16,6 +16,7 @@ class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
'types': TypeSerializer,
}
permission_classes = (IsValidUser,)
default_limit = None
def get_queryset(self):
return AllTypes.categories()

View File

@@ -14,6 +14,7 @@ class FavoriteAssetViewSet(BulkModelViewSet):
serializer_class = FavoriteAssetSerializer
permission_classes = (IsValidUser,)
filterset_fields = ['asset']
default_limit = None
def dispatch(self, request, *args, **kwargs):
with tmp_to_root_org():

View File

@@ -7,7 +7,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from assets.const import AllTypes
from assets.models import Platform, Node, Asset, PlatformProtocol
from assets.models import Platform, Node, Asset, PlatformProtocol, PlatformAutomation
from assets.serializers import PlatformSerializer, PlatformProtocolSerializer, PlatformListSerializer
from common.api import JMSModelViewSet
from common.permissions import IsValidUser
@@ -43,6 +43,7 @@ class AssetPlatformViewSet(JMSModelViewSet):
'ops_methods': 'assets.view_platform',
'filter_nodes_assets': 'assets.view_platform',
}
default_limit = None
def get_queryset(self):
# 因为没有走分页逻辑,所以需要这里 prefetch
@@ -112,6 +113,7 @@ class PlatformProtocolViewSet(JMSModelViewSet):
class PlatformAutomationMethodsApi(generics.ListAPIView):
permission_classes = (IsValidUser,)
queryset = PlatformAutomation.objects.none()
@staticmethod
def automation_methods():

View File

@@ -1,8 +1,8 @@
from rest_framework.generics import ListAPIView
from assets import serializers
from assets.const import Protocol
from common.permissions import IsValidUser
from assets.models import Protocol
__all__ = ['ProtocolListApi']

View File

@@ -161,6 +161,7 @@ class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
'GET': 'assets.view_asset',
'list': 'assets.view_asset',
}
queryset = Node.objects.none()
def get_assets(self):
key = self.request.query_params.get('key')

View File

@@ -201,14 +201,17 @@ class PlaybookPrepareMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# example: {'gather_fact_windows': {'id': 'gather_fact_windows', 'name': '', 'method': 'gather_fact', ...} }
self.method_id_meta_mapper = {
self.method_id_meta_mapper = self.get_method_id_meta_mapper()
# 根据执行方式就行分组, 不同资产的改密、推送等操作可能会使用不同的执行方式
# 然后根据执行方式分组, 再根据 bulk_size 分组, 生成不同的 playbook
self.playbooks = []
def get_method_id_meta_mapper(self):
return {
method["id"]: method
for method in self.platform_automation_methods
if method["method"] == self.__class__.method_type()
}
# 根据执行方式就行分组, 不同资产的改密、推送等操作可能会使用不同的执行方式
# 然后根据执行方式分组, 再根据 bulk_size 分组, 生成不同的 playbook
self.playbooks = []
@classmethod
def method_type(cls):

View File

@@ -0,0 +1,13 @@
- hosts: website
gather_facts: no
vars:
ansible_python_interpreter: "{{ local_python_interpreter }}"
tasks:
- name: Test Website connection
website_ping:
login_host: "{{ jms_asset.address }}"
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
steps: "{{ params.steps }}"
load_state: "{{ params.load_state }}"

View File

@@ -0,0 +1,50 @@
id: website_ping
name: "{{ 'Website ping' | trans }}"
method: ping
category:
- web
type:
- website
params:
- name: load_state
type: choice
label: "{{ 'Load state' | trans }}"
choices:
- [ networkidle, "{{ 'Network idle' | trans }}" ]
- [ domcontentloaded, "{{ 'Dom content loaded' | trans }}" ]
- [ load, "{{ 'Load completed' | trans }}" ]
default: 'load'
- name: steps
type: list
default: []
label: "{{ 'Steps' | trans }}"
help_text: "{{ 'Params step help text' | trans }}"
i18n:
Website ping:
zh: 使用 Playwright 模拟浏览器测试可连接性
en: Use Playwright to simulate a browser for connectivity testing
ja: Playwright を使用してブラウザをシミュレートし、接続性テストを実行する
Load state:
zh: 加载状态检测
en: Load state detection
ja: ロード状態の検出
Steps:
zh: 步骤
en: Steps
ja: 手順
Network idle:
zh: 网络空闲
en: Network idle
ja: ネットワークが空いた状態
Dom content loaded:
zh: 文档内容加载完成
en: Dom content loaded
ja: ドキュメントの内容がロードされた状態
Load completed:
zh: 全部加载完成
en: All load completed
ja: すべてのロードが完了した状態
Params step help text:
zh: 配置步骤,根据配置决定任务执行步骤
ja: パラメータを設定し、設定に基づいてタスクの実行手順を決定します
en: Configure steps, and determine the task execution steps based on the configuration.

View File

@@ -20,13 +20,17 @@ class WebTypes(BaseType):
def _get_automation_constrains(cls) -> dict:
constrains = {
'*': {
'ansible_enabled': False,
'ping_enabled': False,
'ansible_enabled': True,
'ansible_config': {
'ansible_connection': 'local',
},
'ping_enabled': True,
'gather_facts_enabled': False,
'verify_account_enabled': False,
'change_secret_enabled': False,
'verify_account_enabled': True,
'change_secret_enabled': True,
'push_account_enabled': False,
'gather_accounts_enabled': False,
'remove_account_enabled': False,
}
}
return constrains

View File

@@ -112,7 +112,7 @@ class Protocol(models.Model):
return protocols[0] if len(protocols) > 0 else {}
@property
def setting(self):
def setting(self) -> dict:
if self._setting is not None:
return self._setting
return self.asset_platform_protocol.get('setting', {})
@@ -122,7 +122,7 @@ class Protocol(models.Model):
self._setting = value
@property
def public(self):
def public(self) -> bool:
return self.asset_platform_protocol.get('public', True)
@@ -210,7 +210,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
return self.category == const.Category.DS and hasattr(self, 'ds')
@lazyproperty
def spec_info(self):
def spec_info(self) -> dict:
instance = getattr(self, self.category, None)
if not instance:
return {}
@@ -240,7 +240,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
return info
@lazyproperty
def auto_config(self):
def auto_config(self) -> dict:
platform = self.platform
auto_config = {
'su_enabled': platform.su_enabled,
@@ -343,11 +343,11 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
return names
@lazyproperty
def type(self):
def type(self) -> str:
return self.platform.type
@lazyproperty
def category(self):
def category(self) -> str:
return self.platform.category
def is_category(self, category):

View File

@@ -573,7 +573,7 @@ class Node(JMSOrgBaseModel, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
return not self.__gt__(other)
@property
def name(self):
def name(self) -> str:
return self.value
def computed_full_value(self):

View File

@@ -25,7 +25,7 @@ class PlatformProtocol(models.Model):
return '{}/{}'.format(self.name, self.port)
@property
def secret_types(self):
def secret_types(self) -> list:
return Protocol.settings().get(self.name, {}).get('secret_types', ['password'])
@lazyproperty

View File

@@ -147,6 +147,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=())
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Accounts'))
nodes_display = NodeDisplaySerializer(read_only=False, required=False, label=_("Node path"))
auto_config = serializers.DictField(read_only=True, label=_('Auto info'))
platform = ObjectRelatedField(queryset=Platform.objects, required=True, label=_('Platform'),
attrs=('id', 'name', 'type'))
accounts_amount = serializers.IntegerField(read_only=True, label=_('Accounts amount'))
@@ -425,6 +426,18 @@ class DetailMixin(serializers.Serializer):
gathered_info = MethodSerializer(label=_('Gathered info'), read_only=True)
auto_config = serializers.DictField(read_only=True, label=_('Auto info'))
@staticmethod
def get_auto_config(obj) -> dict:
return obj.auto_config
@staticmethod
def get_gathered_info(obj) -> dict:
return obj.gathered_info
@staticmethod
def get_spec_info(obj) -> dict:
return obj.spec_info
def get_instance(self):
request = self.context.get('request')
if not self.instance and UUID_PATTERN.findall(request.path):

View File

@@ -19,11 +19,13 @@ __all__ = [
class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
assets = ObjectRelatedField(many=True, required=False, queryset=Asset.objects, label=_('Assets'))
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
executed_amount = serializers.IntegerField(read_only=True, label=_('Executed amount'))
class Meta:
read_only_fields = [
'date_created', 'date_updated', 'created_by',
'periodic_display', 'executed_amount', 'type', 'last_execution_date'
'periodic_display', 'executed_amount', 'type',
'last_execution_date',
]
mini_fields = [
'id', 'name', 'type', 'is_periodic', 'interval',

View File

@@ -1,6 +1,6 @@
import os
import uuid
from datetime import timedelta
from datetime import timedelta, datetime
from importlib import import_module
from django.conf import settings
@@ -40,7 +40,7 @@ __all__ = [
class JobLog(JobExecution):
@property
def creator_name(self):
def creator_name(self) -> str:
return self.creator.name
class Meta:
@@ -232,7 +232,7 @@ class UserLoginLog(models.Model):
return '%s(%s)' % (self.username, self.city)
@property
def backend_display(self):
def backend_display(self) -> str:
return gettext(self.backend)
@classmethod
@@ -258,7 +258,7 @@ class UserLoginLog(models.Model):
return login_logs
@property
def reason_display(self):
def reason_display(self) -> str:
from authentication.errors import reason_choices, old_reason_choices
reason = reason_choices.get(self.reason)
@@ -300,15 +300,15 @@ class UserSession(models.Model):
return '%s(%s)' % (self.user, self.ip)
@property
def backend_display(self):
def backend_display(self) -> str:
return gettext(self.backend)
@property
def is_active(self):
def is_active(self) -> bool:
return user_session_manager.check_active(self.key)
@property
def date_expired(self):
def date_expired(self) -> datetime:
session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore
session_store = session_store_cls(session_key=self.key)
cache_key = session_store.cache_key

View File

@@ -119,11 +119,11 @@ class OperateLogSerializer(BulkOrgResourceModelSerializer):
fields = fields_small
@staticmethod
def get_resource_type(instance):
def get_resource_type(instance) -> str:
return _(instance.resource_type)
@staticmethod
def get_resource(instance):
def get_resource(instance) -> str:
return i18n_trans(instance.resource)
@@ -147,11 +147,11 @@ class ActivityUnionLogSerializer(serializers.Serializer):
r_type = serializers.CharField(read_only=True)
@staticmethod
def get_timestamp(obj):
def get_timestamp(obj) -> str:
return as_current_tz(obj['datetime']).strftime('%Y-%m-%d %H:%M:%S')
@staticmethod
def get_content(obj):
def get_content(obj) -> str:
if not obj['r_detail']:
action = obj['r_action'].replace('_', ' ').capitalize()
ctn = _('%s %s this resource') % (obj['r_user'], _(action).lower())
@@ -160,7 +160,7 @@ class ActivityUnionLogSerializer(serializers.Serializer):
return ctn
@staticmethod
def get_detail_url(obj):
def get_detail_url(obj) -> str:
detail_url = ''
detail_id, obj_type = obj['r_detail_id'], obj['r_type']
if not detail_id:
@@ -210,7 +210,7 @@ class UserSessionSerializer(serializers.ModelSerializer):
"backend_display": {"label": _("Auth backend display")},
}
def get_is_current_user_session(self, obj):
def get_is_current_user_session(self, obj) -> bool:
request = self.context.get('request')
if not request:
return False

View File

@@ -2,15 +2,14 @@
#
import datetime
import os
import subprocess
from celery import shared_task
from django.conf import settings
from django.core.files.storage import default_storage
from django.db import transaction
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils._os import safe_join
from django.utils.translation import gettext_lazy as _
from common.const.crontab import CRONTAB_AT_AM_TWO
from common.storage.ftp_file import FTPFileStorageHandler
@@ -79,7 +78,7 @@ def clean_celery_tasks_period():
command = "find %s -mtime +%s -name '*.log' -type f -exec rm -f {} \\;"
safe_run_cmd(command, (settings.CELERY_LOG_DIR, expire_days))
celery_log_path = safe_join(settings.LOG_DIR, 'celery.log')
command = "echo > {}".format(celery_log_path)
command = "echo > %s"
safe_run_cmd(command, (celery_log_path,))

View File

@@ -618,6 +618,8 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
token_id = request.data.get('id') or ''
token = ConnectionToken.get_typed_connection_token(token_id)
if not token:
raise PermissionDenied('Token {} is not valid'.format(token))
token.is_valid()
serializer = self.get_serializer(instance=token)

View File

@@ -14,7 +14,9 @@ class TempTokenAuthBackend(JMSBaseAuthBackend):
return settings.AUTH_TEMP_TOKEN
def authenticate(self, request, username='', password=''):
token = self.model.objects.filter(username=username, secret=password).first()
tokens = self.model.objects.filter(username=username).order_by('-date_created')[:500]
token = next((t for t in tokens if t.secret == password), None)
if not token:
return None
if not token.is_valid:

View File

@@ -4,6 +4,24 @@ import authentication.models.access_key
import common.db.fields
from django.db import migrations
old_access_key_secrets_mapper = {}
def fetch_access_key_secrets(apps, schema_editor):
AccessKey = apps.get_model("authentication", "AccessKey")
for id, secret in AccessKey.objects.all().values_list('id', 'secret'):
old_access_key_secrets_mapper[str(id)] = secret
def save_access_key_secrets(apps, schema_editor):
AccessKey = apps.get_model("authentication", "AccessKey")
aks = AccessKey.objects.filter(id__in=list(old_access_key_secrets_mapper.keys()))
for ak in aks:
old_value = old_access_key_secrets_mapper.get(str(ak.id))
if not old_value:
continue
ak.secret = old_value
ak.save(update_fields=["secret"])
class Migration(migrations.Migration):
@@ -12,6 +30,7 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(fetch_access_key_secrets),
migrations.AlterField(
model_name="accesskey",
name="secret",
@@ -27,4 +46,5 @@ class Migration(migrations.Migration):
verbose_name="Secret"
),
),
migrations.RunPython(save_access_key_secrets),
]

View File

@@ -4,6 +4,7 @@ from datetime import timedelta
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import models
from django.shortcuts import get_object_or_404
from django.utils import timezone
@@ -76,7 +77,10 @@ class ConnectionToken(JMSOrgBaseModel):
@classmethod
def get_typed_connection_token(cls, token_id):
token = get_object_or_404(cls, id=token_id)
try:
token = get_object_or_404(cls, id=token_id)
except ValidationError:
return None
if token.type == ConnectionTokenType.ADMIN.value:
token = AdminConnectionToken.objects.get(id=token_id)
@@ -85,11 +89,11 @@ class ConnectionToken(JMSOrgBaseModel):
return token
@property
def is_expired(self):
def is_expired(self) -> bool:
return self.date_expired < timezone.now()
@property
def expire_time(self):
def expire_time(self) -> int:
interval = self.date_expired - timezone.now()
seconds = interval.total_seconds()
if seconds < 0:
@@ -161,7 +165,7 @@ class ConnectionToken(JMSOrgBaseModel):
def expire_at(self):
return self.permed_account.date_expired.timestamp()
def is_valid(self):
def is_valid(self) -> bool:
if not self.is_active:
error = _('Connection token inactive')
raise PermissionDenied(error)

View File

@@ -20,9 +20,10 @@ class UserConfirmation(permissions.BasePermission):
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
return True
confirm_level = request.session.get('CONFIRM_LEVEL')
confirm_type = request.session.get('CONFIRM_TYPE')
confirm_time = request.session.get('CONFIRM_TIME')
session = getattr(request, 'session', {})
confirm_level = session.get('CONFIRM_LEVEL')
confirm_type = session.get('CONFIRM_TYPE')
confirm_time = session.get('CONFIRM_TIME')
ttl = self.get_ttl(confirm_type)
now = int(time.time())

View File

@@ -60,7 +60,7 @@ class _ConnectionTokenAccountSerializer(serializers.ModelSerializer):
]
@staticmethod
def get_su_from(account):
def get_su_from(account) -> dict:
if not hasattr(account, 'asset'):
return {}
su_enabled = account.asset.platform.su_enabled

View File

@@ -6,6 +6,7 @@ from common.serializers import CommonModelSerializer
from common.serializers.fields import EncryptedField
from perms.serializers.permission import ActionChoicesField
from ..models import ConnectionToken, AdminConnectionToken
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
__all__ = [
'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer',
@@ -13,7 +14,7 @@ __all__ = [
]
class ConnectionTokenSerializer(CommonModelSerializer):
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
@@ -60,7 +61,7 @@ class ConnectionTokenSerializer(CommonModelSerializer):
validated_data['remote_addr'] = get_request_ip(request)
return super().create(validated_data)
def get_from_ticket_info(self, instance):
def get_from_ticket_info(self, instance) -> dict:
if not instance.from_ticket:
return {}
user = self.get_request_user()

View File

@@ -46,7 +46,7 @@ class BearerTokenSerializer(serializers.Serializer):
user = UserProfileSerializer(read_only=True)
@staticmethod
def get_keyword(obj):
def get_keyword(obj) -> str:
return 'Bearer'
@staticmethod

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import datetime
import os
from typing import Callable
from urllib.parse import urlparse
from urllib.parse import urlparse, urlsplit, urlunsplit, urlencode
from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY
@@ -155,9 +155,18 @@ class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView):
auth_name, redirect_url = auth_method['name'], auth_method['url']
next_url = request.GET.get('next') or '/'
next_url = safe_next_url(next_url, request=request)
query_string = request.GET.urlencode()
redirect_url = '{}?next={}&{}'.format(redirect_url, next_url, query_string)
merged_qs_items = dict(request.GET.lists())
merged_qs_items.pop('next', None)
merged = {}
for k, v_list in merged_qs_items.items():
merged[k] = v_list if len(v_list) > 1 else (v_list[0] if v_list else '')
merged['next'] = next_url
query = urlencode(merged, doseq=True)
u = urlsplit(redirect_url)
redirect_url = urlunsplit((u.scheme, u.netloc, u.path, query, u.fragment))
if settings.LOGIN_REDIRECT_MSG_ENABLED:
message_data = {
'title': _('Redirecting'),
@@ -165,7 +174,7 @@ class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView):
'redirect_url': redirect_url,
'interval': 3,
'has_cancel': True,
'cancel_url': reverse('authentication:login') + f'?admin=1&{query_string}'
'cancel_url': reverse('authentication:login') + f'?admin=1&{query}'
}
redirect_url = FlashMessageUtil.gen_message_url(message_data)
return redirect_url

View File

@@ -143,7 +143,9 @@ class EncryptMixin:
if value is None:
return value
plain_value = Encryptor(value).decrypt()
encryptor = Encryptor(value)
plain_value = encryptor.decrypt()
# 可能和Json mix所以要先解密再json
sp = super()
if hasattr(sp, "from_db_value"):
@@ -166,9 +168,6 @@ class EncryptMixin:
class EncryptTextField(EncryptMixin, models.TextField):
description = _("Encrypt field using Secret Key")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class EncryptCharField(EncryptMixin, models.CharField):
@staticmethod

View File

@@ -1,4 +1,5 @@
from contextlib import contextmanager
import base64
from django.db import connections, transaction, connection
from django.utils.encoding import force_str
@@ -102,6 +103,54 @@ class Encryptor:
def __init__(self, value):
self.value = force_str(value)
def is_encrypted_data(self):
"""
检测数据是否为加密格式
返回 True 表示是加密数据False 表示是原始数据
"""
if not self.value:
return False
# 检测 base64 编码格式 (crypto.encrypt 的输出)
try:
# 尝试不同的 base64 解码方式
# 1. 标准 base64
try:
base64.b64decode(self.value)
return True
except Exception:
pass
# 2. URL-safe base64
try:
# 添加必要的填充
missing_padding = len(self.value) % 4
if missing_padding:
padded_value = self.value + '=' * (4 - missing_padding)
else:
padded_value = self.value
base64.urlsafe_b64decode(padded_value)
return True
except Exception:
pass
except Exception:
pass
# 检测 AES GCM 格式 (固定72字符metadata)
if len(self.value) > 72:
try:
# 前72字符应该是3个24字符的base64编码
metadata = self.value[:72]
for i in range(0, 72, 24):
part = metadata[i:i+24]
base64.b64decode(part)
return True
except Exception:
pass
return False
def decrypt(self):
plain_value = crypto.decrypt(self.value)

View File

@@ -5,7 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Model
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.fields import ChoiceField, empty
from rest_framework.fields import empty
from common.db.fields import TreeChoices, JSONManyToManyField as ModelJSONManyToManyField
from common.utils import decrypt_password, is_uuid
@@ -53,7 +53,7 @@ class EncryptedField(serializers.CharField):
return decrypt_password(value)
class LabeledChoiceField(ChoiceField):
class LabeledChoiceField(serializers.ChoiceField):
def to_representation(self, key):
if key is None:
return key
@@ -180,6 +180,122 @@ class ObjectRelatedField(serializers.RelatedField):
except (TypeError, ValueError):
self.fail("incorrect_type", data_type=type(pk).__name__)
def get_schema(self):
"""
为 drf-spectacular 提供 OpenAPI schema
"""
# 获取字段的基本信息
field_type = 'array' if self.many else 'object'
if field_type == 'array':
# 如果是多对多关系
return {
'type': 'array',
'items': self._get_openapi_item_schema(),
'description': getattr(self, 'help_text', ''),
'title': getattr(self, 'label', ''),
}
else:
# 如果是一对一关系
return {
'type': 'object',
'properties': self._get_openapi_properties_schema(),
'description': getattr(self, 'help_text', ''),
'title': getattr(self, 'label', ''),
}
def _get_openapi_item_schema(self):
"""
获取数组项的 OpenAPI schema
"""
return self._get_openapi_object_schema()
def _get_openapi_object_schema(self):
"""
获取对象的 OpenAPI schema
"""
properties = {}
# 动态分析 attrs 中的属性类型
for attr in self.attrs:
# 尝试从 queryset 的 model 中获取字段信息
field_type = self._infer_field_type(attr)
properties[attr] = {
'type': field_type,
'description': f'{attr} field'
}
return {
'type': 'object',
'properties': properties,
'required': ['id'] if 'id' in self.attrs else []
}
def _infer_field_type(self, attr_name):
"""
智能推断字段类型
"""
try:
# 如果有 queryset尝试从 model 中获取字段信息
if hasattr(self, 'queryset') and self.queryset is not None:
model = self.queryset.model
if hasattr(model, '_meta') and hasattr(model._meta, 'fields'):
field = model._meta.get_field(attr_name)
if field:
return self._map_django_field_type(field)
except Exception:
pass
# 如果没有 queryset 或无法获取字段信息,使用启发式规则
return self._heuristic_field_type(attr_name)
def _map_django_field_type(self, field):
"""
将 Django 字段类型映射到 OpenAPI 类型
"""
field_type = type(field).__name__
# 整数类型
if 'Integer' in field_type or 'BigInteger' in field_type or 'SmallInteger' in field_type:
return 'integer'
# 浮点数类型
elif 'Float' in field_type or 'Decimal' in field_type:
return 'number'
# 布尔类型
elif 'Boolean' in field_type:
return 'boolean'
# 日期时间类型
elif 'DateTime' in field_type or 'Date' in field_type or 'Time' in field_type:
return 'string'
# 文件类型
elif 'File' in field_type or 'Image' in field_type:
return 'string'
# 其他类型默认为字符串
else:
return 'string'
def _heuristic_field_type(self, attr_name):
"""
启发式推断字段类型
"""
# 基于属性名的启发式规则
if attr_name in ['is_active', 'enabled', 'visible'] or attr_name.startswith('is_'):
return 'boolean'
elif attr_name in ['count', 'number', 'size', 'amount']:
return 'integer'
elif attr_name in ['price', 'rate', 'percentage']:
return 'number'
else:
# 默认返回字符串类型
return 'string'
def _get_openapi_properties_schema(self):
"""
获取对象属性的 OpenAPI schema
"""
return self._get_openapi_object_schema()['properties']
class TreeChoicesField(serializers.MultipleChoiceField):
def __init__(self, choice_cls, **kwargs):
@@ -238,6 +354,23 @@ class BitChoicesField(TreeChoicesField):
value |= name_value_map[name]
return value
def get_schema(self):
"""
为 drf-spectacular 提供 OpenAPI schema
"""
return {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'value': {'type': 'string'},
'label': {'type': 'string'}
}
},
'description': getattr(self, 'help_text', ''),
'title': getattr(self, 'label', ''),
}
def run_validation(self, data=empty):
"""
备注:

View File

@@ -185,3 +185,9 @@ def check_migrations_file_prefix_conflict(*args, **kwargs):
print(f'{msg_left}{msg_right1}\n{msg_right2}\n')
print('=' * 80)
@receiver(django_ready)
def clear_response_cache(sender, **kwargs):
from django.core.cache import cache
cache.delete_pattern('views.decorators.cache.cache_page:*')

View File

@@ -3,14 +3,14 @@
import os
from azure.storage.blob import BlobServiceClient
from .base import ObjectStorage
class AzureStorage(ObjectStorage):
def __init__(self, config):
from azure.storage.blob import BlobServiceClient
self.account_name = config.get("ACCOUNT_NAME", None)
self.account_key = config.get("ACCOUNT_KEY", None)
self.container_name = config.get("CONTAINER_NAME", None)

View File

@@ -10,7 +10,7 @@ import socket
import time
import uuid
from collections import OrderedDict
from functools import wraps
from functools import wraps, cached_property
from itertools import chain
import html2text
@@ -246,17 +246,19 @@ def dict_get_any(d, keys):
return None
class lazyproperty:
def __init__(self, func):
self.func = func
# class lazyproperty:
# def __init__(self, func):
# self.func = func
def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
# def __get__(self, instance, cls):
# if instance is None:
# return self
# else:
# value = self.func(instance)
# setattr(instance, self.func.__name__, value)
# return value
lazyproperty = cached_property
def get_disk_usage(path):

View File

@@ -47,7 +47,6 @@ class Subscription:
self.ch = pb.ch
self.sub = sub
self.unsubscribed = False
logger.info(f"Subscribed to channel: {sub}")
def _handle_msg(self, _next, error, complete):
"""

View File

@@ -1,9 +1,8 @@
import re
import subprocess
import shlex
import subprocess
def safe_run_cmd(cmd_str, cmd_args=(), shell=True):
cmd_args = [shlex.quote(arg) for arg in cmd_args]
cmd_args = [shlex.quote(str(arg)) for arg in cmd_args]
cmd = cmd_str % tuple(cmd_args)
return subprocess.run(cmd, shell=shell)
return subprocess.run(cmd, shell=shell)

View File

@@ -70,7 +70,7 @@
"SessionLockedMessage": "This session is locked by %s, please wait for the session to be unlocked.",
"SessionUnlockedMessage": "This session has been unlocked by %s.",
"ShowProperties": "Show properties",
"StopHotKey": "Stop (Ctrl + C)",
"StopHotKey": "Stop (Ctrl + D)",
"Submit": "Submit",
"Total": "Total",
"Type": "Type",

View File

@@ -70,7 +70,7 @@
"SessionLockedMessage": "此会话已被 %s 锁定,无法继续执行命令",
"SessionUnlockedMessage": "此会话已被 %s 解锁,可以继续执行命令",
"ShowProperties": "属性",
"StopHotKey": "停止 (Ctrl + C)",
"StopHotKey": "停止 (Ctrl + D)",
"Submit": "提交",
"Total": "总计",
"Type": "类型",

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -199,7 +199,7 @@ msgstr ""
msgid "Skip"
msgstr ""
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr ""
@@ -572,8 +572,8 @@ msgstr ""
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1015,7 +1015,7 @@ msgstr ""
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1183,13 +1183,13 @@ msgstr ""
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128 ops/models/job.py:155
#: authentication/serializers/connect_token_secret.py:130 ops/models/job.py:155
#: perms/serializers/user_permission.py:28 terminal/models/applet/applet.py:40
#: terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1248,7 +1248,7 @@ msgid "Spec info"
msgstr ""
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1263,7 +1263,7 @@ msgstr ""
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1812,7 +1812,7 @@ msgstr ""
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99 audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2461,7 +2461,7 @@ msgstr ""
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr ""
@@ -2482,7 +2482,7 @@ msgstr ""
msgid "Gathered info"
msgstr ""
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr ""
@@ -2654,7 +2654,7 @@ msgstr ""
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2663,7 +2663,7 @@ msgstr ""
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -2977,7 +2977,7 @@ msgid "Disk total"
msgstr ""
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr ""
@@ -3261,7 +3261,7 @@ msgstr ""
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr ""
@@ -3287,7 +3287,7 @@ msgstr ""
msgid "Rename dir"
msgstr ""
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3296,7 +3296,7 @@ msgstr ""
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr ""
@@ -4112,7 +4112,7 @@ msgid "Input secret"
msgstr ""
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4222,38 +4222,38 @@ msgstr ""
msgid "binding reminder"
msgstr ""
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "Builtin"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr ""
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr ""
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr ""
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr ""
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr ""
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr ""
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr ""
@@ -6136,27 +6136,27 @@ msgstr ""
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "System Admin"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "System Auditor"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "System Component"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "Organization Admin"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "Organization Auditor"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "Organization user"
@@ -6192,6 +6192,30 @@ msgstr ""
msgid "Can view System Tools"
msgstr ""
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr ""
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr ""
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr ""
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr ""
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr ""
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr ""
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr ""
@@ -6309,24 +6333,28 @@ msgstr ""
msgid "Job audit"
msgstr ""
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr ""
#: rbac/tree.py:181
msgid "App organizations"
msgstr "Organizations"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr ""
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr ""
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr ""
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr ""
@@ -6354,6 +6382,22 @@ msgstr ""
msgid "Account automation report"
msgstr ""
#: reports/views.py:44
msgid "ConsoleDashboard"
msgstr ""
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
msgid "ChangeSecretDashboard"
msgstr ""
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr ""
@@ -7583,11 +7627,11 @@ msgid ""
msgstr ""
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr ""
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr ""
#: settings/serializers/security.py:173
@@ -9265,7 +9309,7 @@ msgstr ""
msgid "Body"
msgstr ""
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr ""
@@ -9274,7 +9318,7 @@ msgstr ""
msgid "Ticket flow approval rule"
msgstr ""
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -224,7 +224,7 @@ msgstr "Plantilla"
msgid "Skip"
msgstr "Omitir"
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr "Actualizar"
@@ -599,8 +599,8 @@ msgstr "Actividad de cuenta"
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1054,7 +1054,7 @@ msgstr "Reglas de contraseña"
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1239,13 +1239,13 @@ msgstr "Categoría"
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128
#: authentication/serializers/connect_token_secret.py:130
#: ops/models/job.py:155 perms/serializers/user_permission.py:28
#: terminal/models/applet/applet.py:40 terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1304,7 +1304,7 @@ msgid "Spec info"
msgstr "Información especial"
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1320,7 +1320,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1924,7 +1924,7 @@ msgstr ""
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99
#: audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2608,7 +2608,7 @@ msgstr "Dirección"
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr "Plataforma"
@@ -2629,7 +2629,7 @@ msgstr "Nodo"
msgid "Gathered info"
msgstr "Recopilar información sobre hardware de activos"
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr "Atributos personalizados"
@@ -2801,7 +2801,7 @@ msgstr "Sistema"
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2810,7 +2810,7 @@ msgstr "Valor"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -3146,7 +3146,7 @@ msgid "Disk total"
msgstr "Tamaño del disco"
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr "Sistema operativo"
@@ -3454,7 +3454,7 @@ msgstr "Eliminar directorio"
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr "Eliminar"
@@ -3480,7 +3480,7 @@ msgstr "Descargar"
msgid "Rename dir"
msgstr "Mapear directorio"
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3489,7 +3489,7 @@ msgstr "Ver"
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr "Crear"
@@ -4361,7 +4361,7 @@ msgid "Input secret"
msgstr "Contraseña personalizada"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4471,38 +4471,38 @@ msgstr "Recordatorio de inicio de sesión remoto"
msgid "binding reminder"
msgstr "Recordatorio de vinculación"
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "Incorporado"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr "Opciones"
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "Componentes"
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr "Dominio"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr "Expiración inmediata"
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "Nombre de imagen"
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "Puerto de imagen"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "Protocolo de imagen"
@@ -6535,27 +6535,27 @@ msgstr "{} debe tener al menos un rol del sistema"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "Administrador del sistema"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "Auditor del sistema"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "Componente del sistema"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "Administrador de la organización"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "Auditor de la organización"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "Usuario de la organización"
@@ -6591,6 +6591,30 @@ msgstr "Puede ver la gestión de archivos"
msgid "Can view System Tools"
msgstr "Puede ver las herramientas del sistema"
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr "Puedes consultar el informe de inicio de sesión de usuarios"
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr "puedes consultar el informe de cambio de contraseña de usuarios"
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr "puedes consultar el informe de estadísticas de activos"
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr "puedes consultar el informe de actividades de activos"
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr "puedes consultar el informe de estadísticas de cuentas"
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr "puedes consultar el informe de automatización de cuentas"
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr "Tipo de contenido"
@@ -6710,24 +6734,28 @@ msgstr "Licencia"
msgid "Job audit"
msgstr "Auditoría de acciones"
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr "Informe"
#: rbac/tree.py:181
msgid "App organizations"
msgstr "Gestión de organizaciones"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr "Comentarios de órdenes de trabajo"
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "Orden de trabajo"
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr "Configuración general"
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr "Ver árbol de autorizaciones"
@@ -6755,6 +6783,26 @@ msgstr "Informe de estadísticas de cuentas"
msgid "Account automation report"
msgstr "Informe de automatización de cuentas"
#: reports/views.py:44
#, fuzzy
#| msgid "Console"
msgid "ConsoleDashboard"
msgstr "Consola"
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
#, fuzzy
#| msgid "Change secret record"
msgid "ChangeSecretDashboard"
msgstr "Registro de cambio de clave"
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr "Chat AI no está habilitado"
@@ -8218,11 +8266,11 @@ msgstr ""
"completar la autenticación"
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr "Activar código de verificación para inicio de sesión"
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr ""
"Habilitar el código de verificación para prevenir el inicio de sesión de "
"bots"
@@ -10044,7 +10092,7 @@ msgstr "Nombre de usuario"
msgid "Body"
msgstr "Contenido"
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "Nivel de aprobación"
@@ -10053,7 +10101,7 @@ msgstr "Nivel de aprobación"
msgid "Ticket flow approval rule"
msgstr "Información de aprobación de la orden de trabajo"
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr "Proceso de la orden de trabajo"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -202,7 +202,7 @@ msgstr "テンプレート"
msgid "Skip"
msgstr "スキップ"
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr "更新"
@@ -590,8 +590,8 @@ msgstr "アカウントの活動"
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1038,7 +1038,7 @@ msgstr "パスワードルール"
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1209,13 +1209,13 @@ msgstr "カテゴリ"
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128
#: authentication/serializers/connect_token_secret.py:130
#: ops/models/job.py:155 perms/serializers/user_permission.py:28
#: terminal/models/applet/applet.py:40 terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1274,7 +1274,7 @@ msgid "Spec info"
msgstr "特別情報"
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1290,7 +1290,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1835,7 +1835,7 @@ msgstr "1-100、低い値は最初に一致します"
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99
#: audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2492,7 +2492,7 @@ msgstr "アドレス"
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr "プラットフォーム"
@@ -2513,7 +2513,7 @@ msgstr "ノード"
msgid "Gathered info"
msgstr "資産ハードウェア情報の収集"
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr "カスタム属性"
@@ -2685,7 +2685,7 @@ msgstr "システム"
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2694,7 +2694,7 @@ msgstr "値"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -3019,7 +3019,7 @@ msgid "Disk total"
msgstr "ディスクの合計"
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr "OS"
@@ -3303,7 +3303,7 @@ msgstr "Rmdir"
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr "削除"
@@ -3329,7 +3329,7 @@ msgstr "ダウンロード"
msgid "Rename dir"
msgstr "マップディレクトリ"
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3338,7 +3338,7 @@ msgstr "表示"
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr "作成"
@@ -4155,7 +4155,7 @@ msgid "Input secret"
msgstr "カスタムパスワード"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4265,38 +4265,38 @@ msgstr "異なる都市ログインのリマインダー"
msgid "binding reminder"
msgstr "バインディングリマインダー"
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "ビルトイン"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr "オプション"
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "コンポーネント"
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr "ドメイン"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr "すぐに期限切れ"
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "ミラー名"
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "ミラーポート"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "ミラープロトコル"
@@ -6184,27 +6184,27 @@ msgstr "{} 少なくとも1つのシステムロール"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "システム管理者"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "システム監査人"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "システムコンポーネント"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "組織管理者"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "監査員を組織する"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "組織ユーザー"
@@ -6240,6 +6240,30 @@ msgstr "ファイルマネージャを表示できます"
msgid "Can view System Tools"
msgstr "システムツールを表示できます"
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr "ユーザーのログインレポートを確認できます"
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr "ユーザーのパスワード変更レポートを確認できます"
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr "資産統計レポートを確認できます"
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr "資産活動レポートを確認できます"
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr "アカウント統計レポートを確認できます"
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr "アカウント自動化レポートを確認できます"
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr "コンテンツタイプ"
@@ -6357,24 +6381,28 @@ msgstr "ライセンス"
msgid "Job audit"
msgstr "作業監査"
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr "レポート"
#: rbac/tree.py:181
msgid "App organizations"
msgstr "アプリ組織"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr "チケットコメント"
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "チケット"
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr "共通設定"
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr "権限ツリーの表示"
@@ -6402,6 +6430,26 @@ msgstr "アカウント統計報告"
msgid "Account automation report"
msgstr "アカウント自動化報告"
#: reports/views.py:44
#, fuzzy
#| msgid "Console"
msgid "ConsoleDashboard"
msgstr "Console"
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
#, fuzzy
#| msgid "Change secret record"
msgid "ChangeSecretDashboard"
msgstr "パスワード レコードの変更"
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr "チャットAIがオンになっていない"
@@ -7679,11 +7727,11 @@ msgid ""
msgstr "パスワードと追加コードは、検証のためにサードパーティの認証システムに送信されます"
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr "ログインcaptchaの有効化"
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr "Captchaを有効にしてロボット認証を防止する"
#: settings/serializers/security.py:173
@@ -9386,7 +9434,7 @@ msgstr "ユーザー表示名"
msgid "Body"
msgstr "ボディ"
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "レベルを承認する"
@@ -9395,7 +9443,7 @@ msgstr "レベルを承認する"
msgid "Ticket flow approval rule"
msgstr "チケットフロー承認ルール"
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr "チケットの流れ"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -202,7 +202,7 @@ msgstr "템플릿"
msgid "Skip"
msgstr "건너뛰기"
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr "업데이트"
@@ -575,8 +575,8 @@ msgstr "계정 활동"
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1023,7 +1023,7 @@ msgstr "암호 규칙"
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1195,13 +1195,13 @@ msgstr "카테고리"
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128
#: authentication/serializers/connect_token_secret.py:130
#: ops/models/job.py:155 perms/serializers/user_permission.py:28
#: terminal/models/applet/applet.py:40 terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1260,7 +1260,7 @@ msgid "Spec info"
msgstr "특별 정보"
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1276,7 +1276,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1825,7 +1825,7 @@ msgstr "우선순위 선택 범위는 1-100 (숫자가 작을수록 더 우선)"
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99
#: audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2484,7 +2484,7 @@ msgstr "주소"
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr "플랫폼"
@@ -2505,7 +2505,7 @@ msgstr "노드"
msgid "Gathered info"
msgstr "자산 하드웨어 정보 수집"
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr "사용자 정의 속성"
@@ -2677,7 +2677,7 @@ msgstr "시스템"
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2686,7 +2686,7 @@ msgstr "값"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -3008,7 +3008,7 @@ msgid "Disk total"
msgstr "하드디스크 크기"
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr "운영 체제"
@@ -3294,7 +3294,7 @@ msgstr "디렉터리 삭제"
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr "삭제"
@@ -3320,7 +3320,7 @@ msgstr "다운로드"
msgid "Rename dir"
msgstr "디렉터리 매핑"
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3329,7 +3329,7 @@ msgstr "조회"
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr "생성"
@@ -4154,7 +4154,7 @@ msgid "Input secret"
msgstr "비밀번호 사용자 정의"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4264,38 +4264,38 @@ msgstr "이전 로그인 알림"
msgid "binding reminder"
msgstr "바인딩 알림"
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "내장된"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr "옵션"
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "구성 요소"
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr "도메인"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr "즉시 만료"
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "이미지 이름"
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "이미지 포트"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "이미지 프로토콜"
@@ -6206,27 +6206,27 @@ msgstr "{} 최소한 하나의 시스템 역할이 필요합니다."
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "시스템 관리자"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "시스템 감사자"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "시스템 구성 요소"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "조직 관리자"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "조직 감사자"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "조직 사용자"
@@ -6276,6 +6276,30 @@ msgstr ""
msgid "Can view System Tools"
msgstr "시스템 도구를 확인할 수 있습니다"
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr "사용자 로그인 보고서를 확인할 수 있습니다"
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr "사용자 비밀번호 변경 보고서를 확인할 수 있습니다"
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr "자산 통계 보고서를 확인할 수 있습니다"
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr "자산 활동 보고서를 확인할 수 있습니다"
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr "계정 통계 보고서를 확인할 수 있습니다"
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr "계정 자동화 보고서를 확인할 수 있습니다"
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr "내용 유형"
@@ -6393,24 +6417,28 @@ msgstr "라이센스"
msgid "Job audit"
msgstr "작업 감사"
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr "보고서"
#: rbac/tree.py:181
msgid "App organizations"
msgstr "조직 관리"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr "작업 댓글"
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "작업"
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr "일반 설정"
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr "권한 트리 보기"
@@ -6438,6 +6466,26 @@ msgstr "계정 통계 보고서"
msgid "Account automation report"
msgstr "계정 자동화 보고서"
#: reports/views.py:44
#, fuzzy
#| msgid "Console"
msgid "ConsoleDashboard"
msgstr "콘솔"
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
#, fuzzy
#| msgid "Change secret record"
msgid "ChangeSecretDashboard"
msgstr "비밀번호 변경 기록"
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr "채팅 AI가 활성화되어 있지 않습니다"
@@ -7741,11 +7789,11 @@ msgstr ""
"인증을 완료해야 합니다."
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr "로그인 인증 코드 활성화"
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr "인증 코드를 활성화하여 로봇 로그인을 차단합니다."
#: settings/serializers/security.py:173
@@ -9451,7 +9499,7 @@ msgstr "사용자 표시 이름"
msgid "Body"
msgstr "내용"
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "승인 수준"
@@ -9460,7 +9508,7 @@ msgstr "승인 수준"
msgid "Ticket flow approval rule"
msgstr "작업 요청 승인 정보"
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr "작업 요청 프로세스"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -202,7 +202,7 @@ msgstr "Modelo"
msgid "Skip"
msgstr "Pular"
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr "Atualizar"
@@ -578,8 +578,8 @@ msgstr "Atividade da conta"
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1046,7 +1046,7 @@ msgstr "Regras de senha"
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1228,13 +1228,13 @@ msgstr "Categoria"
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128
#: authentication/serializers/connect_token_secret.py:130
#: ops/models/job.py:155 perms/serializers/user_permission.py:28
#: terminal/models/applet/applet.py:40 terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1293,7 +1293,7 @@ msgid "Spec info"
msgstr "Informações especiais"
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1309,7 +1309,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1900,7 +1900,7 @@ msgstr ""
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99
#: audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2577,7 +2577,7 @@ msgstr "Endereço"
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr "Plataforma"
@@ -2598,7 +2598,7 @@ msgstr "Node"
msgid "Gathered info"
msgstr "Coletar informações do hardware do ativo"
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr "Propriedades personalizadas"
@@ -2770,7 +2770,7 @@ msgstr "Sistema"
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2779,7 +2779,7 @@ msgstr "Valor"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -3111,7 +3111,7 @@ msgid "Disk total"
msgstr "Tamanho do disco rígido"
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr "Sistema operacional"
@@ -3417,7 +3417,7 @@ msgstr "Excluir diretório"
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr "Excluir"
@@ -3443,7 +3443,7 @@ msgstr "Baixar"
msgid "Rename dir"
msgstr "Mapeamento de Diretórios"
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3452,7 +3452,7 @@ msgstr "Visualizar"
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr "Criar"
@@ -4300,7 +4300,7 @@ msgid "Input secret"
msgstr "Senha personalizada"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4410,38 +4410,38 @@ msgstr "Alerta de login remoto"
msgid "binding reminder"
msgstr "Alerta de vinculação"
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "Incorporado"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr "Opções"
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "Componentes"
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr "Domínio da Web"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr "Expiração Imediata"
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "Nome da Imagem"
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "Porta da Imagem"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "Protocolo da Imagem"
@@ -6428,27 +6428,27 @@ msgstr "{} tem pelo menos um papel de sistema"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "Administrador do sistema"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "Auditor do sistema"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "Componentes do sistema"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "Administrador da organização"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "Auditor da organização"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "Organizar usuários"
@@ -6484,6 +6484,30 @@ msgstr "Pode visualizar a gestão de arquivos"
msgid "Can view System Tools"
msgstr "Pode visualizar as ferramentas do sistema"
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr "Pode visualizar o relatório de login do usuário"
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr "Pode visualizar o relatório de alteração de senha do usuário"
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr "Pode visualizar o relatório de estatísticas de ativos"
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr "Pode visualizar o relatório de atividades de ativos"
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr "Pode visualizar o relatório de estatísticas de contas"
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr "Pode visualizar o relatório de automação de contas."
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr "Tipo de conteúdo"
@@ -6603,24 +6627,28 @@ msgstr "Licença"
msgid "Job audit"
msgstr "Auditoria de Tarefas"
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr "Relatório"
#: rbac/tree.py:181
msgid "App organizations"
msgstr "Gerenciamento de Organização"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr "Comentários de Ordem de Serviço"
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "Ordem de Serviço"
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr "Configurações Gerais"
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr "Visualizar Árvore de Autorização"
@@ -6648,6 +6676,26 @@ msgstr "Relatório de Estatísticas de Contas"
msgid "Account automation report"
msgstr "Relatório de Automação de Contas"
#: reports/views.py:44
#, fuzzy
#| msgid "Console"
msgid "ConsoleDashboard"
msgstr "Console"
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
#, fuzzy
#| msgid "Change secret record"
msgid "ChangeSecretDashboard"
msgstr "Registro de alteração de senha"
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr "Chat AI Não Ativado"
@@ -8031,11 +8079,11 @@ msgstr ""
"de terceiros exigem senha + 6 números para completar a autenticação"
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr "Habilitar código de verificação de login"
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr "Ligar o código de verificação para prevenir o login de robôs"
#: settings/serializers/security.py:173
@@ -9821,7 +9869,7 @@ msgstr "Nome de exibição do usuário"
msgid "Body"
msgstr "Conteúdo"
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "Nível de aprovação"
@@ -9830,7 +9878,7 @@ msgstr "Nível de aprovação"
msgid "Ticket flow approval rule"
msgstr "Informações de aprovação da ordem de serviço"
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr "Fluxo de Action da ordem de serviço"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: 2025-06-11 11:51+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -204,7 +204,7 @@ msgstr "Шаблон"
msgid "Skip"
msgstr "Пропустить"
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr "Обновить"
@@ -579,8 +579,8 @@ msgstr "Активность аккаунта"
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1033,7 +1033,7 @@ msgstr "Правила паролей"
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1215,13 +1215,13 @@ msgstr "Категория"
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128
#: authentication/serializers/connect_token_secret.py:130
#: ops/models/job.py:155 perms/serializers/user_permission.py:28
#: terminal/models/applet/applet.py:40 terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1280,7 +1280,7 @@ msgid "Spec info"
msgstr "Специальная информация"
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1296,7 +1296,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1877,7 +1877,7 @@ msgstr "1-100 (меньшее число - выше приоритет)"
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99
#: audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2553,7 +2553,7 @@ msgstr "Адрес"
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr "Платформа"
@@ -2574,7 +2574,7 @@ msgstr "Папки"
msgid "Gathered info"
msgstr "Собранные данные"
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr "Пользовательские атрибуты"
@@ -2746,7 +2746,7 @@ msgstr "Система"
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2755,7 +2755,7 @@ msgstr "Значение"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -3090,7 +3090,7 @@ msgid "Disk total"
msgstr "Размер диска"
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr "ОС"
@@ -3399,7 +3399,7 @@ msgstr "Удалить каталог"
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr "Удалить"
@@ -3425,7 +3425,7 @@ msgstr "Скачать"
msgid "Rename dir"
msgstr "Сопоставление каталога"
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3434,7 +3434,7 @@ msgstr "Просмотр"
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr "Создать"
@@ -4280,7 +4280,7 @@ msgid "Input secret"
msgstr "Введите пароль"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4390,38 +4390,38 @@ msgstr "Уведомление о входе из другого местопо
msgid "binding reminder"
msgstr "Уведомление о привязке"
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "Встроенный"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr "Параметры"
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "Компоненты"
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr "Зона"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr "Истекает немедленно"
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "Название образа"
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "Порт образа"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "Протокол образа"
@@ -6402,27 +6402,27 @@ msgstr "{} хотя бы одна системная роль"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "Системный администратор"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "Системный аудитор"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "Системные компоненты"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "Администратор организации"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "Аудитор организации"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "Пользователь организации"
@@ -6458,6 +6458,30 @@ msgstr "Просмотр файлового менеджера"
msgid "Can view System Tools"
msgstr "Просмотр системных инструментов"
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr "Можно просматривать отчеты о входе пользователей"
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr "Можно просматривать отчеты о смене пароля пользователей"
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr "Можно просматривать отчеты о статистике активов"
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr "Можно просматривать отчеты о действиях с активами"
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr "Можно просматривать отчеты о статистике аккаунтов"
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr "Можно просматривать отчеты о автоматизации аккаунтов"
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr "Тип содержимого"
@@ -6577,24 +6601,28 @@ msgstr "Лицензия"
msgid "Job audit"
msgstr "Аудит заданий"
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr "Отчёт"
#: rbac/tree.py:181
msgid "App organizations"
msgstr "Управление организациями"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr "Комментарии к заявке"
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "Заявка"
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr "Общие настройки"
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr "Просмотр дерева разрешений"
@@ -6622,6 +6650,26 @@ msgstr "Отчет о статистике аккаунтов"
msgid "Account automation report"
msgstr "Автоматизированный отчет по аккаунтам"
#: reports/views.py:44
#, fuzzy
#| msgid "Console"
msgid "ConsoleDashboard"
msgstr "Консоль"
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
#, fuzzy
#| msgid "Change secret record"
msgid "ChangeSecretDashboard"
msgstr "Запись о смене секрета"
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr "Чат AI не включен"
@@ -7998,11 +8046,11 @@ msgstr ""
"для проверки"
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr "Проверочный код (captcha) при входе"
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr "Включите проверочный код, чтобы предотвратить аутентификацию роботов"
#: settings/serializers/security.py:173
@@ -9786,7 +9834,7 @@ msgstr "Имя пользователя"
msgid "Body"
msgstr "Содержимое"
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "Уровень одобрения"
@@ -9795,7 +9843,7 @@ msgstr "Уровень одобрения"
msgid "Ticket flow approval rule"
msgstr "Правило утверждения потока заявок"
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr "Поток заявок"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -201,7 +201,7 @@ msgstr "模板"
msgid "Skip"
msgstr "跳过"
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr "更新"
@@ -574,8 +574,8 @@ msgstr "账号活动"
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1017,7 +1017,7 @@ msgstr "密码规则"
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1194,13 +1194,13 @@ msgstr "类别"
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128 ops/models/job.py:155
#: authentication/serializers/connect_token_secret.py:130 ops/models/job.py:155
#: perms/serializers/user_permission.py:28 terminal/models/applet/applet.py:40
#: terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1259,7 +1259,7 @@ msgid "Spec info"
msgstr "特殊信息"
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1274,7 +1274,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1841,7 +1841,7 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)"
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99 audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2501,7 +2501,7 @@ msgstr "地址"
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr "平台"
@@ -2522,7 +2522,7 @@ msgstr "节点"
msgid "Gathered info"
msgstr "收集资产硬件信息"
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr "自定义属性"
@@ -2696,7 +2696,7 @@ msgstr "系统"
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2705,7 +2705,7 @@ msgstr "值"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -3034,7 +3034,7 @@ msgid "Disk total"
msgstr "硬盘大小"
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr "操作系统"
@@ -3323,7 +3323,7 @@ msgstr "删除目录"
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr "删除"
@@ -3349,7 +3349,7 @@ msgstr "下载"
msgid "Rename dir"
msgstr "映射目录"
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3358,7 +3358,7 @@ msgstr "查看"
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr "创建"
@@ -4181,7 +4181,7 @@ msgid "Input secret"
msgstr "自定义密码"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4291,38 +4291,38 @@ msgstr "异地登录提醒"
msgid "binding reminder"
msgstr "绑定提醒"
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "内置的"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr "选项"
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "组件"
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr "网域"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr "立刻过期"
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "镜像名称"
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "镜像端口"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "镜像协议"
@@ -6239,27 +6239,27 @@ msgstr "{} 至少有一个系统角色"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "系统管理员"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "系统审计员"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "系统组件"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "组织管理员"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "组织审计员"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "组织用户"
@@ -6295,6 +6295,30 @@ msgstr "可以查看文件管理"
msgid "Can view System Tools"
msgstr "可以查看系统工具"
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr "可以查看用户登录报告"
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr "可以查看用户改密报告"
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr "可以查看资产统计报告"
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr "可以查看资产活动报告"
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr "可以查看账号统计报告"
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr "可以查看账号自动化报告"
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr "内容类型"
@@ -6412,24 +6436,28 @@ msgstr "许可证"
msgid "Job audit"
msgstr "作业审计"
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr "报表"
#: rbac/tree.py:181
msgid "App organizations"
msgstr "组织管理"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr "工单评论"
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "工单"
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr "一般设置"
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr "查看授权树"
@@ -6457,6 +6485,26 @@ msgstr "账号统计报告"
msgid "Account automation report"
msgstr "账号自动化报告"
#: reports/views.py:44
#, fuzzy
#| msgid "Console"
msgid "ConsoleDashboard"
msgstr "控制台"
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
#, fuzzy
#| msgid "Change secret record"
msgid "ChangeSecretDashboard"
msgstr "改密记录"
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr "聊天 AI 没有开启"
@@ -7475,7 +7523,9 @@ msgstr "登录到邮件服务器的用户名。这通常是你的邮件地址"
msgid ""
"Password to use for the email server. It is used in conjunction with "
"`Account` when authenticating to the email server"
msgstr "用于电子邮件服务器的密码。在向电子邮件服务器进行身份验证时,它与`账号`一起使用"
msgstr ""
"用于电子邮件服务器的密码。在向电子邮件服务器进行身份验证时,它与`账号`一起使"
"用"
#: settings/serializers/msg.py:41
msgid "Sender"
@@ -7739,11 +7789,11 @@ msgstr ""
"码+6位数字 完成认证"
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr "启用登录验证码"
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr "开启验证码,防止机器人登录"
#: settings/serializers/security.py:173
@@ -9468,7 +9518,7 @@ msgstr "用户显示名称"
msgid "Body"
msgstr "内容"
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "审批级别"
@@ -9477,7 +9527,7 @@ msgstr "审批级别"
msgid "Ticket flow approval rule"
msgstr "工单批准信息"
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr "工单流程"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-21 11:01+0800\n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -202,7 +202,7 @@ msgstr "模板"
msgid "Skip"
msgstr "跳過"
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:291
#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
msgid "Update"
msgstr "更新"
@@ -575,8 +575,8 @@ msgstr "帳號活動"
#: assets/serializers/platform.py:283
#: authentication/backends/passkey/models.py:10
#: authentication/models/ssh_key.py:12
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: ops/mixin.py:32 ops/models/adhoc.py:19 ops/models/celery.py:15
#: ops/models/celery.py:81 ops/models/job.py:147 ops/models/playbook.py:28
#: ops/models/variable.py:9 ops/serializers/job.py:20
@@ -1023,7 +1023,7 @@ msgstr "密碼規則"
#: accounts/models/base.py:69 assets/models/automations/base.py:28
#: assets/models/cmd_filter.py:39 assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:119
#: authentication/serializers/connect_token_secret.py:121
#: terminal/models/applet/applet.py:41
#: terminal/models/virtualapp/virtualapp.py:23 users/serializers/user.py:257
msgid "Is active"
@@ -1195,13 +1195,13 @@ msgstr "類別"
#: assets/serializers/platform.py:160 assets/serializers/platform.py:172
#: audits/serializers.py:76 audits/serializers.py:196
#: authentication/models/connection_token.py:66
#: authentication/serializers/connect_token_secret.py:128
#: authentication/serializers/connect_token_secret.py:130
#: ops/models/job.py:155 perms/serializers/user_permission.py:28
#: terminal/models/applet/applet.py:40 terminal/models/component/storage.py:58
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:30
#: terminal/serializers/session.py:33 terminal/serializers/storage.py:281
#: terminal/serializers/storage.py:294 tickets/models/comment.py:26
#: tickets/models/flow.py:42 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/ticket/general.py:276 tickets/serializers/flow.py:25
#: tickets/serializers/ticket/ticket.py:19
msgid "Type"
@@ -1260,7 +1260,7 @@ msgid "Spec info"
msgstr "特殊資訊"
#: accounts/serializers/account/account.py:473
#: authentication/serializers/connect_token_secret.py:162
#: authentication/serializers/connect_token_secret.py:164
#: authentication/templates/authentication/_access_key_modal.html:30
#: perms/models/perm_node.py:21 users/serializers/group.py:33
msgid "ID"
@@ -1276,7 +1276,7 @@ msgstr "ID"
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:58 perms/models/asset_permission.py:63
#: rbac/builtin.py:127 rbac/models/rolebinding.py:49
#: rbac/builtin.py:133 rbac/models/rolebinding.py:49
#: rbac/serializers/rolebinding.py:17 terminal/backends/command/models.py:16
#: terminal/models/session/session.py:27 terminal/models/session/sharing.py:34
#: terminal/notifications.py:157 terminal/notifications.py:217
@@ -1817,7 +1817,7 @@ msgstr "優先度可選範圍為 1-100 (數值越小越優先)"
#: acls/models/base.py:41 acls/serializers/base.py:57
#: assets/models/cmd_filter.py:81 audits/models.py:99
#: audits/serializers.py:107
#: authentication/serializers/connect_token_secret.py:121
#: authentication/serializers/connect_token_secret.py:123
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:63 perms/serializers/permission.py:85
#: terminal/backends/command/models.py:24
@@ -2472,7 +2472,7 @@ msgstr "地址"
#: assets/models/asset/common.py:169 assets/models/platform.py:155
#: assets/serializers/asset/common.py:150
#: authentication/backends/passkey/models.py:12
#: authentication/serializers/connect_token_secret.py:120
#: authentication/serializers/connect_token_secret.py:122
#: perms/serializers/user_permission.py:26 xpack/plugins/cloud/models.py:398
msgid "Platform"
msgstr "系統平台"
@@ -2493,7 +2493,7 @@ msgstr "節點"
msgid "Gathered info"
msgstr "收集資產硬體資訊"
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:15
msgid "Custom info"
msgstr "自訂屬性"
@@ -2667,7 +2667,7 @@ msgstr "系統"
#: assets/serializers/cagegory.py:11 assets/serializers/cagegory.py:18
#: assets/serializers/cagegory.py:24
#: authentication/models/connection_token.py:34
#: authentication/serializers/connect_token_secret.py:127
#: authentication/serializers/connect_token_secret.py:129
#: common/serializers/common.py:86 labels/models.py:12 settings/models.py:40
#: users/models/preference.py:13
msgid "Value"
@@ -2676,7 +2676,7 @@ msgstr "值"
#: assets/models/label.py:40 assets/serializers/cagegory.py:10
#: assets/serializers/cagegory.py:17 assets/serializers/cagegory.py:23
#: assets/serializers/platform.py:159
#: authentication/serializers/connect_token_secret.py:126
#: authentication/serializers/connect_token_secret.py:128
#: common/serializers/common.py:85 labels/serializers.py:45
#: settings/serializers/msg.py:90 xpack/plugins/cloud/models.py:403
msgid "Label"
@@ -3000,7 +3000,7 @@ msgid "Disk total"
msgstr "硬碟大小"
#: assets/serializers/asset/info/gathered.py:16
#: authentication/serializers/connect_token_secret.py:117
#: authentication/serializers/connect_token_secret.py:119
msgid "OS"
msgstr "操作系統"
@@ -3282,7 +3282,7 @@ msgstr "刪除目錄"
#: audits/const.py:14 audits/const.py:25
#: authentication/templates/authentication/_access_key_modal.html:65
#: rbac/tree.py:284
#: rbac/tree.py:292
msgid "Delete"
msgstr "刪除"
@@ -3308,7 +3308,7 @@ msgstr "下載"
msgid "Rename dir"
msgstr "映射目錄"
#: audits/const.py:23 rbac/tree.py:282 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: terminal/templates/terminal/_msg_command_warning.html:18
#: terminal/templates/terminal/_msg_session_sharing.html:10
#: xpack/plugins/cloud/manager.py:102
@@ -3317,7 +3317,7 @@ msgstr "查看"
#: audits/const.py:26
#: authentication/templates/authentication/_access_key_modal.html:22
#: rbac/tree.py:281
#: rbac/tree.py:289
msgid "Create"
msgstr "創建"
@@ -4131,7 +4131,7 @@ msgid "Input secret"
msgstr "自訂密碼"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:116
#: authentication/serializers/connect_token_secret.py:118
#: terminal/models/applet/applet.py:43
#: terminal/models/virtualapp/virtualapp.py:24
#: terminal/serializers/session.py:31 terminal/serializers/session.py:58
@@ -4241,38 +4241,38 @@ msgstr "異地登錄提醒"
msgid "binding reminder"
msgstr "綁定提醒"
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:120
msgid "Is builtin"
msgstr "內建的"
#: authentication/serializers/connect_token_secret.py:122
#: authentication/serializers/connect_token_secret.py:124
msgid "Options"
msgstr "選項"
#: authentication/serializers/connect_token_secret.py:129
#: authentication/serializers/connect_token_secret.py:131
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "組件"
#: authentication/serializers/connect_token_secret.py:138
#: authentication/serializers/connect_token_secret.py:140
msgid "Domain"
msgstr "網域"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:142
msgid "Expired now"
msgstr "立刻過期"
#: authentication/serializers/connect_token_secret.py:173
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "鏡像名稱"
#: authentication/serializers/connect_token_secret.py:174
#: authentication/serializers/connect_token_secret.py:176
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "鏡像埠"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:177
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "鏡像協議"
@@ -6158,27 +6158,27 @@ msgstr "{} 至少有一個系統角色"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:118
#: rbac/builtin.py:124
msgid "SystemAdmin"
msgstr "系統管理員"
#: rbac/builtin.py:121
#: rbac/builtin.py:127
msgid "SystemAuditor"
msgstr "系統審計員"
#: rbac/builtin.py:124
#: rbac/builtin.py:130
msgid "SystemComponent"
msgstr "系統組件"
#: rbac/builtin.py:130
#: rbac/builtin.py:136
msgid "OrgAdmin"
msgstr "組織管理員"
#: rbac/builtin.py:133
#: rbac/builtin.py:139
msgid "OrgAuditor"
msgstr "組織審計員"
#: rbac/builtin.py:136
#: rbac/builtin.py:142
msgid "OrgUser"
msgstr "組織用戶"
@@ -6214,6 +6214,30 @@ msgstr "文件管理"
msgid "Can view System Tools"
msgstr "可以查看系統工具"
#: rbac/models/menu.py:22
msgid "Can view user login report"
msgstr "可以查看使用者登入報告"
#: rbac/models/menu.py:23
msgid "Can view user change password report"
msgstr "可以查看使用者變更密碼報告"
#: rbac/models/menu.py:24
msgid "Can view asset statistics report"
msgstr "可以查看資產統計報告"
#: rbac/models/menu.py:25
msgid "Can view asset activity report"
msgstr "可以查看資產活動報告"
#: rbac/models/menu.py:26
msgid "Can view account statistics report"
msgstr "可以查看帳號統計報告"
#: rbac/models/menu.py:27
msgid "Can view account automation report"
msgstr "可以查看帳號自動化報告"
#: rbac/models/permission.py:18
msgid "ContentType"
msgstr "內容類型"
@@ -6331,24 +6355,28 @@ msgstr "許可證"
msgid "Job audit"
msgstr "作業審核"
#: rbac/tree.py:173
#: rbac/tree.py:71
msgid "Report"
msgstr "報表"
#: rbac/tree.py:181
msgid "App organizations"
msgstr "組織管理"
#: rbac/tree.py:174
#: rbac/tree.py:182
msgid "Ticket comment"
msgstr "工單評論"
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "工單管理"
#: rbac/tree.py:176
#: rbac/tree.py:184
msgid "Common setting"
msgstr "一般設定"
#: rbac/tree.py:177
#: rbac/tree.py:185
msgid "View permission tree"
msgstr "查看授權樹"
@@ -6376,6 +6404,26 @@ msgstr "賬號統計報告"
msgid "Account automation report"
msgstr "賬號自動化報告"
#: reports/views.py:44
#, fuzzy
#| msgid "Console"
msgid "ConsoleDashboard"
msgstr "控制台"
#: reports/views.py:48
msgid "AuditsDashboard"
msgstr ""
#: reports/views.py:52
msgid "PamDashboard"
msgstr ""
#: reports/views.py:56
#, fuzzy
#| msgid "Change secret record"
msgid "ChangeSecretDashboard"
msgstr "改密記錄"
#: settings/api/chat.py:41
msgid "Chat AI is not enabled"
msgstr "聊天 AI 沒有開啟"
@@ -7628,11 +7676,11 @@ msgid ""
msgstr "密碼和附加碼一併發送給第三方認證系統進行校驗, 如:有的第三方認證系統,需要 密碼+6位數字 完成認證"
#: settings/serializers/security.py:169
msgid "Login captcha"
msgid "Login CAPTCHA"
msgstr "啟用登入驗證碼"
#: settings/serializers/security.py:170
msgid "Enable captcha to prevent robot authentication"
msgid "Enable CAPTCHA to prevent robot authentication"
msgstr "開啟驗證碼,防止機器人登錄"
#: settings/serializers/security.py:173
@@ -9312,7 +9360,7 @@ msgstr "用戶顯示名稱"
msgid "Body"
msgstr "內容"
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "審批級別"
@@ -9321,7 +9369,7 @@ msgstr "審批級別"
msgid "Ticket flow approval rule"
msgstr "工單批准資訊"
#: tickets/models/flow.py:52
#: tickets/models/flow.py:53
msgid "Ticket flow"
msgstr "工單流程"

View File

@@ -102,7 +102,7 @@
"Aliyun": "Alibaba cloud",
"All": "All",
"AllAccountTip": "All accounts already added on the asset",
"AllAccounts": "All existing accounts",
"AllAccounts": "All accounts",
"AllClickRead": "Mark all as read",
"AllMembers": "All members",
"AllowInvalidCert": "Ignore certificate check",
@@ -310,7 +310,9 @@
"ChatAI": "Chat AI",
"ChatHello": "Hello, can I help you?",
"ChdirHelpText": "By default, the execution directory is the user's home directory",
"Check": "Check",
"CheckAssetsAmount": "Check asset quantity",
"CheckResponse": "Check the response",
"CheckViewAcceptor": "Click to view the acceptance person",
"CleanHelpText": "A scheduled cleanup task will be carried out every day at 2 a.m. the data cleaned up will not be recoverable",
"Cleaning": "Regular clean-up",
@@ -320,6 +322,7 @@
"ClearSecret": "Clear secret",
"ClearSelection": "Clear selection",
"ClearSuccessMsg": "Clear successful",
"Click": "Click",
"ClickCopy": "Click to copy",
"ClientCertificate": "Client certificate",
"Clipboard": "Clipboard",
@@ -575,6 +578,7 @@
"Exclude": "Does not include",
"ExcludeAsset": "Skipped assets",
"ExcludeSymbol": "Exclude char",
"ExcludeAccount": "Exclude accounts",
"ExecCloudSyncErrorMsg": "The cloud account configuration is incomplete, please update and try again.",
"Execute": "Execute",
"ExecuteAfterSaving": "Execute after saving",
@@ -811,6 +815,7 @@
"LoginTitleTip": "Note: it will be displayed on the enterprise edition user ssh login koko login page (e.g.: welcome to use jumpserver open source PAM)",
"LoginUserRanking": "Login user ranking",
"LoginUserToday": "Users logged today",
"LoginUsername": "Login username",
"LoginUsers": "Active account",
"LogoIndexTip": "Tip: it will be displayed in the upper left corner of the page (recommended image size: 185px*55px)",
"LogoLogoutTip": "Tip: it will be displayed on the web terminal page of enterprise edition users (recommended image size: 82px*82px)",
@@ -1150,6 +1155,9 @@
"ResolveSelected": "Resolve selected",
"Resource": "Resources",
"ResourceType": "Resource type",
"Response": "Response",
"ResponseExpression": "Response body expression",
"ResponseExpressionTip": "For the response body {count: 1}, the expression can be written as count=1 to match\nFor the response body {data: [a, b]}, the expression can be written as data.length=2 to match\nFor the response body {data: [{username: admin}]}, the expression can be written as data.0.username=admin to match",
"RestoreButton": "Restore",
"RestoreDefault": "Reset to default",
"RestoreDialogMessage": "Are you sure you want to restore to default initialization?",
@@ -1250,6 +1258,7 @@
"Selected": "Selected",
"Selection": "Selection",
"Selector": "Selector",
"SelectorPlaceholder": "Enter the element locator (supports CSS/XPath)",
"Send": "Send",
"SendVerificationCode": "Send verification code",
"SerialNumber": "Serial number",
@@ -1299,6 +1308,7 @@
"Skipped": "Skipped",
"Slack": "Slack",
"SlackOAuth": "Slack OAuth",
"Sleep": "Sleep",
"Source": "Source",
"SourceIP": "Source address",
"SourcePort": "Source port",
@@ -1312,6 +1322,7 @@
"State": "Status",
"StateClosed": "Is closed",
"Status": "Status",
"StatusCode": "Status code",
"StatusGreen": "Recently in good condition",
"StatusRed": "Last task execution failed",
"StatusYellow": "There have been recent failures",
@@ -1550,6 +1561,7 @@
"UsersAndUserGroups": "Users/groups",
"UsersTotal": "Total users",
"Valid": "Valid",
"Value": "Value",
"Variable": "Variable",
"VariableHelpText": "You can use {{ key }} to read built-in variables in commands",
"VariableName": "Variable name",
@@ -1574,6 +1586,8 @@
"VisitTimeDistribution": "Visit time distribution",
"Visits": "Visits",
"Volcengine": "Volcengine",
"WaitForSelector": "Wait for the element",
"WaitForURL": "Wait for the URL",
"Warning": "Warning",
"WatermarkVariableHelpText": "You can use ${key} to read built-in variables in watermark content",
"WeChat": "WeChat",
@@ -1616,4 +1630,4 @@
"setVariable": "Set variable",
"userId": "User ID",
"userName": "User name"
}
}

View File

@@ -309,7 +309,9 @@
"ChatAI": "Consulta Inteligente",
"ChatHello": "¡Hola! ¿En qué puedo ayudarte?",
"ChdirHelpText": "El directorio de ejecución predeterminado es el directorio home del usuario ejecutivo",
"Check": "Verificar",
"CheckAssetsAmount": "Verificar la cantidad de activos",
"CheckResponse": "Verificar la respuesta",
"CheckViewAcceptor": "Haz clic para ver al receptor",
"CleanHelpText": "La tarea de limpieza programada se ejecutará todos los días a las 2 de la mañana; los datos eliminados no podrán ser recuperados.",
"Cleaning": "Limpiar regularmente",
@@ -319,6 +321,7 @@
"ClearSecret": "Limpiar texto cifrado",
"ClearSelection": "Limpiar selección",
"ClearSuccessMsg": "Limpieza exitosa",
"Click": "Hacer clic",
"ClickCopy": "Hacer clic para copiar",
"ClientCertificate": "Certificado de cliente",
"Clipboard": "Portapapeles",
@@ -812,6 +815,7 @@
"LoginTitleTip": "Nota: Se mostrará en la página de inicio de sesión de SSH de usuarios de la versión empresarial de KoKo (por ejemplo: Bienvenido a JumpServer, la puerta de enlace de código abierto)",
"LoginUserRanking": "Clasificación de cuentas",
"LoginUserToday": "Número de usuarios que han iniciado sesión hoy",
"LoginUsername": "Nombre de usuario de inicio de sesión",
"LoginUsers": "Cuentas activas",
"LogoIndexTip": "Sugerencia: se mostrará en la parte superior izquierda de la página de gestión (se recomienda un tamaño de imagen de: 185px*55px)",
"LogoLogoutTip": "Nota: Se mostrará en la página del terminal web del usuario de la versión empresarial (se recomienda un tamaño de imagen de: 82px*82px)",
@@ -1155,6 +1159,9 @@
"ResolveSelected": "Resolver lo establecido",
"Resource": "Recursos",
"ResourceType": "Tipo de recurso",
"Response": "Cuerpo de la respuesta",
"ResponseExpression": "Expresión del cuerpo de la respuesta",
"ResponseExpressionTip": "Para el cuerpo de la respuesta {count: 1}, la expresión se puede escribir como count=1 para hacer coincidencia\nPara el cuerpo de la respuesta {data: [a, b]}, la expresión se puede escribir como data.length=2 para hacer coincidencia\nPara el cuerpo de la respuesta {data: [{username: admin}]}, la expresión se puede escribir como data.0.username=admin para hacer coincidencia",
"RestoreButton": "Restaurar predeterminados",
"RestoreDefault": "Restablecer a predeterminado",
"RestoreDialogMessage": "¿Está seguro de que desea restablecer la inicialización predeterminada?",
@@ -1257,6 +1264,7 @@
"Selected": "Seleccionado",
"Selection": "Seleccionar",
"Selector": "Selector",
"SelectorPlaceholder": "Ingrese el localizador de elemento (admite CSS/XPath)",
"Send": "Enviar",
"SendVerificationCode": "Enviar código de verificación",
"SerialNumber": "Número de serie",
@@ -1306,6 +1314,7 @@
"Skipped": "Saltado",
"Slack": "Slack",
"SlackOAuth": "Autenticación de Slack",
"Sleep": "Esperar",
"Source": "Fuente",
"SourceIP": "Dirección de origen",
"SourcePort": "Puerto de origen",
@@ -1319,6 +1328,7 @@
"State": "Estado",
"StateClosed": "ha cerrado",
"Status": "Estado",
"StatusCode": "Código de estado",
"StatusGreen": "Estado reciente bueno",
"StatusRed": "La última tarea se ejecutó con errores",
"StatusYellow": "Recientemente se han producido fallos en la ejecución",
@@ -1557,6 +1567,7 @@
"UsersAndUserGroups": "Usuario/Grupo de usuarios",
"UsersTotal": "número total de usuarios",
"Valid": "válido",
"Value": "Valor",
"Variable": "Variable",
"VariableHelpText": "Puede usar {{ key }} en el comando para leer variables integradas",
"VariableName": "Nombre de variable",
@@ -1581,6 +1592,8 @@
"VisitTimeDistribution": "Distribución de períodos de acceso",
"Visits": "Visitas",
"Volcengine": "Motor de volcán",
"WaitForSelector": "Esperar el elemento",
"WaitForURL": "Esperar la URL",
"Warning": "Advertencia",
"Watermark": "Marca de agua",
"WatermarkVariableHelpText": "Puede utilizar ${key} en el contenido del sello personalizado para leer las variables integradas como la lista de contraseñas débiles, la hora actual, la disponibilidad de la página de gestión, el ID de usuario, el nombre de usuario, la dirección de activos, el nombre de usuario, el sello, el ID de activos y el nombre de activos.",
@@ -1625,4 +1638,4 @@
"setVariable": "configurar parámetros",
"userId": "ID de usuario",
"userName": "Nombre de usuario"
}
}

View File

@@ -313,7 +313,9 @@
"ChatAI": "スマートアンサー",
"ChatHello": "こんにちは!お手伝いできることがあれば何でもお申し付けください。",
"ChdirHelpText": "デフォルトの実行ディレクトリは実行ユーザーのホームディレクトリです",
"Check": "確認",
"CheckAssetsAmount": "資産の数量を確認",
"CheckResponse": "レスポンスを確認する",
"CheckViewAcceptor": "受付人を見るにはクリック",
"CleanHelpText": "定期的なクリーンアップタスクは毎日午前2時に実行され、クリーンアップ後のデータは回復できません",
"Cleaning": "定期的にクリーニング",
@@ -323,6 +325,7 @@
"ClearSecret": "暗号文のクリア",
"ClearSelection": "選択をクリア",
"ClearSuccessMsg": "クリアに成功",
"Click": "クリックする",
"ClickCopy": "クリックでコピー",
"ClientCertificate": "クライアント証明書",
"Clipboard": "クリップボード",
@@ -817,6 +820,7 @@
"LoginTitleTip": "ヒント企業版ユーザーのSSHログイン KoKo ログインページに表示されますJumpServerオープンソースバスチオンへようこそ",
"LoginUserRanking": "ログインアカウントランキング",
"LoginUserToday": "今日のログインユーザー数",
"LoginUsername": "ログインユーザー名",
"LoginUsers": "アクティブなアカウント",
"LogoIndexTip": "ヒント:管理ページの左上に表示されます(推奨画像サイズ:185px*55px",
"LogoLogoutTip": "ヒントこれは、エンタープライズ版ユーザーのWebターミナルページに表示されます推奨画像サイズ82px*82px",
@@ -1160,6 +1164,9 @@
"ResolveSelected": "解決選択",
"Resource": "リソース",
"ResourceType": "リソースタイプ",
"Response": "レスポンスボディ",
"ResponseExpression": "レスポンスボディ式",
"ResponseExpressionTip": "レスポンスボディ {count: 1} の場合、式は count=1 と記述して一致させることができます\n レスポンスボディ {data: [a, b]} の場合、式は data.length=2 と記述して一致させることができます\n レスポンスボディ {data: [{username: admin}]} の場合、式は data.0.username=admin と記述して一致させることができます",
"RestoreButton": "デフォルトに戻す",
"RestoreDefault": "デフォルトの復元",
"RestoreDialogMessage": "デフォルトの初期化を復元してもよろしいですか?",
@@ -1262,6 +1269,7 @@
"Selected": "選択済み",
"Selection": "選択可能",
"Selector": "セレクタ",
"SelectorPlaceholder": "要素ロケーターを入力しますCSS/XPath に対応)",
"Send": "送信",
"SendVerificationCode": "認証コードを送信",
"SerialNumber": "シリアルナンバー",
@@ -1311,6 +1319,7 @@
"Skipped": "スキップ済み",
"Slack": "Slack",
"SlackOAuth": "Slack認証",
"Sleep": "待つ",
"Source": "源泉",
"SourceIP": "ソースアドレス",
"SourcePort": "Source Port",
@@ -1324,6 +1333,7 @@
"State": "状態",
"StateClosed": "閉じられた",
"Status": "状況",
"StatusCode": "ステータスコード",
"StatusGreen": "最近の状態は良好",
"StatusRed": "前回のタスク実行は失敗しました",
"StatusYellow": "最近、実行に失敗があり。",
@@ -1562,6 +1572,7 @@
"UsersAndUserGroups": "ユーザー/ユーザーグループ",
"UsersTotal": "総ユーザー数",
"Valid": "有効",
"Value": "値",
"Variable": "変数",
"VariableHelpText": "コマンド中で {{ key }} を使用して内蔵変数を読み取ることができます",
"VariableName": "変数名",
@@ -1586,6 +1597,8 @@
"VisitTimeDistribution": "訪問時間帯の分布",
"Visits": "アクセス数",
"Volcengine": "ヴォルケーノエンジン",
"WaitForSelector": "要素を待つ",
"WaitForURL": "URL を待つ",
"Warning": "警告",
"Watermark": "透かし",
"WatermarkVariableHelpText": "カスタム透かし内容には、${key}を使用して組み込み変数を読み込むことができます。",
@@ -1630,4 +1643,4 @@
"setVariable": "パラメータ設定",
"userId": "ユーザーID",
"userName": "ユーザー名"
}
}

View File

@@ -309,7 +309,9 @@
"ChatAI": "지능형 질문 응답",
"ChatHello": "안녕하세요! 무엇을 도와드릴까요?",
"ChdirHelpText": "기본 실행 디렉토리는 실행 사용자의 홈 디렉토리입니다.",
"Check": "확인하다",
"CheckAssetsAmount": "데이터베이스 프로토콜 포트",
"CheckResponse": "응답을 확인하다",
"CheckViewAcceptor": "수신자 클릭 확인",
"CleanHelpText": "정기 청소 작업은 매일 오전 2시에 실행되며, 청소된 데이터는 복원할 수 없습니다",
"Cleaning": "정기 청소",
@@ -319,6 +321,7 @@
"ClearSecret": "암호 해제",
"ClearSelection": "선택 지우기",
"ClearSuccessMsg": "성공적으로 삭제되었습니다",
"Click": "클릭하다",
"ClickCopy": "클릭하여 복사",
"ClientCertificate": "클라이언트 인증서",
"Clipboard": "클립보드",
@@ -812,6 +815,7 @@
"LoginTitleTip": "提示:기업 버전 사용자 SSH 로그인 KoKo 로그인 페이지에 표시됩니다 (예: JumpServer 오픈 소스 방화벽 기계에 오신 것을 환영합니다)",
"LoginUserRanking": "로그인 계정 순위",
"LoginUserToday": "오늘 로그인 사용자 수",
"LoginUsername": "로그인 사용자 이름",
"LoginUsers": "활성 계정",
"LogoIndexTip": "알림: 관리 페이지 왼쪽 상단에 표시됩니다 (추천 이미지 크기: 185px*55px)",
"LogoLogoutTip": "提示:기업용 사용자 웹 터미널 페이지에 표시됩니다. (이미지 크기는 82px*82px로 설정하는 것이 좋습니다.)",
@@ -1155,6 +1159,9 @@
"ResolveSelected": "선택한 사항 해결",
"Resource": "리소스",
"ResourceType": "자원 유형",
"Response": "응답 본문",
"ResponseExpression": "응답 본문 표현식",
"ResponseExpressionTip": "응답 본문 {count: 1}의 경우 표현식은 count=1로 작성하여 매칭할 수 있습니다\n응답 본문 {data: [a, b]}의 경우 표현식은 data.length=2로 작성하여 매칭할 수 있습니다\n응답 본문 {data: [{username: admin}]}의 경우 표현식은 data.0.username=admin로 작성하여 매칭할 수 있습니다",
"RestoreButton": "기본 설정 복원",
"RestoreDefault": "기본값 복원",
"RestoreDialogMessage": "기본 초기화를 복원하시겠습니까?",
@@ -1257,6 +1264,7 @@
"Selected": "선택됨",
"Selection": "선택 가능",
"Selector": "선택기",
"SelectorPlaceholder": "요소 로케이터를 입력하세요CSS/XPath 지원)",
"Send": "전송",
"SendVerificationCode": "인증 코드 발송",
"SerialNumber": "시리얼 번호",
@@ -1306,6 +1314,7 @@
"Skipped": "건너뛰기",
"Slack": "Slack",
"SlackOAuth": "Slack 인증",
"Sleep": "기다리다",
"Source": "출처",
"SourceIP": "출발지 주소",
"SourcePort": "소스 포트",
@@ -1319,6 +1328,7 @@
"State": "상태",
"StateClosed": "닫힘",
"Status": "상태",
"StatusCode": "상태 코드",
"StatusGreen": "최근 상태 양호",
"StatusRed": "지난 작업 실행 실패",
"StatusYellow": "최근 실행 실패가 있었습니다",
@@ -1557,6 +1567,7 @@
"UsersAndUserGroups": "사용자/사용자 그룹",
"UsersTotal": "사용자 총 수",
"Valid": "유효",
"Value": "값",
"Variable": "변수",
"VariableHelpText": "명령어에서 {{ key }}를 사용하여 내장 변수를 읽을 수 있습니다",
"VariableName": "변수명",
@@ -1581,6 +1592,8 @@
"VisitTimeDistribution": "방문 시간대 분포",
"Visits": "방문량",
"Volcengine": "화산 엔진",
"WaitForSelector": "요소를 기다리다",
"WaitForURL": "URL을 기다리다",
"Warning": "경고",
"Watermark": "워터마크",
"WatermarkVariableHelpText": "사용자가 지정한 워터마크 내용에서 ${key}를 사용하여 내장 변수를 읽을 수 있습니다. \n- 약한 비밀번호 목록 \n- 현재 시간 \n- 관리 페이지 사용 가능 여부 \n- 사용자 ID \n- 사용자 이름 \n- 자산 주소 \n- 사용자명 \n- 워터마크 \n- 자산 ID \n- 자산 이름",
@@ -1625,4 +1638,4 @@
"setVariable": "설정 매개변수",
"userId": "사용자 ID",
"userName": "사용자명"
}
}

View File

@@ -310,7 +310,9 @@
"ChatAI": "Resposta inteligente",
"ChatHello": "Olá! Como posso ajudá-lo? ",
"ChdirHelpText": "O diretório de execução padrão é o diretório home do usuário de execução",
"Check": "Verificar",
"CheckAssetsAmount": "Verificar Quantidade de Ativos",
"CheckResponse": "Verificar a resposta",
"CheckViewAcceptor": " Clique para ver o receptor do pedido ",
"CleanHelpText": "Tarefas de limpeza regulares serão executadas às 2 da manhã todos os dias, os dados após a limpeza não poderão ser restaurados",
"Cleaning": "Limpeza regular",
@@ -320,6 +322,7 @@
"ClearSecret": "Limpar texto cifrado",
"ClearSelection": "Limpar seleção",
"ClearSuccessMsg": "Limpeza bem-sucedida",
"Click": "Clicar",
"ClickCopy": "Clicar para copiar",
"ClientCertificate": "Certificado do cliente",
"Clipboard": "Área de transferência",
@@ -813,6 +816,7 @@
"LoginTitleTip": "Dica: será exibido na página de login SSH do usuário da versão corporativa do KoKo (por exemplo: Bem-vindo ao uso do JumpServer Open Source Bastion)",
"LoginUserRanking": "Classificação de contas de login",
"LoginUserToday": "Número de usuários que fizeram login hoje",
"LoginUsername": "Nome de usuário de login",
"LoginUsers": "Conta ativa",
"LogoIndexTip": "Nota: será exibido no canto superior esquerdo da página de administração (tamanho da imagem recomendado: 185px*55px)",
"LogoLogoutTip": "Dica: Será exibido na página do terminal da Web do usuário da versão corporativa (tamanho sugerido da imagem: 82px*82px)",
@@ -1156,6 +1160,9 @@
"ResolveSelected": "Resolver seleção",
"Resource": "Recursos",
"ResourceType": "Tipo de recurso",
"Response": "Corpo da resposta",
"ResponseExpression": "Expressão do corpo da resposta",
"ResponseExpressionTip": "Para o corpo da resposta {count: 1}, a expressão pode ser escrita como count=1 para fazer correspondência\nPara o corpo da resposta {data: [a, b]}, a expressão pode ser escrita como data.length=2 para fazer correspondência\nPara o corpo da resposta {data: [{username: admin}]}, a expressão pode ser escrita como data.0.username=admin para fazer correspondência",
"RestoreButton": "Restaurar Padrões",
"RestoreDefault": "Restaurar ao padrão",
"RestoreDialogMessage": "Tem certeza de que deseja restaurar para as configurações padrão?",
@@ -1258,6 +1265,7 @@
"Selected": "Selecionado",
"Selection": "Selecionável",
"Selector": "Seletor",
"SelectorPlaceholder": "Insira o localizador de elemento (suporta CSS/XPath)",
"Send": "Enviar",
"SendVerificationCode": "Enviar código de verificação",
"SerialNumber": "Número de Série",
@@ -1307,6 +1315,7 @@
"Skipped": "Ignorado",
"Slack": "Slack",
"SlackOAuth": "Autenticação Slack",
"Sleep": "Esperar",
"Source": "Origem",
"SourceIP": "Endereço de origem",
"SourcePort": "Porta de Origem",
@@ -1320,6 +1329,7 @@
"State": "Status",
"StateClosed": "Desativado",
"Status": "Status",
"StatusCode": "Código de status",
"StatusGreen": "Estado recente é bom",
"StatusRed": "A última tarefa falhou",
"StatusYellow": "Recentemente houve falhas na execução",
@@ -1558,6 +1568,7 @@
"UsersAndUserGroups": "Usuário/Grupo de usuários",
"UsersTotal": " Total de usuários ",
"Valid": "Válido",
"Value": "Valor",
"Variable": "Variáveis",
"VariableHelpText": "Você pode usar {{ key }} no comando para ler a variável integrada",
"VariableName": " Nome da variável ",
@@ -1582,6 +1593,8 @@
"VisitTimeDistribution": "Distribuição dos períodos de acesso",
"Visits": "Visualizações",
"Volcengine": "Motor Volcano",
"WaitForSelector": "Esperar o elemento",
"WaitForURL": "Esperar a URL",
"Warning": "Aviso",
"Watermark": "Marca d'água",
"WatermarkVariableHelpText": "Você pode usar ${key} no conteúdo da marca d'água personalizada para ler variáveis internas como: lista de senhas fracas, hora atual, se a página de gestão está disponível, ID do usuário, nome do usuário, endereço do ativo, nome de usuário, marca d'água, ID do ativo, nome do ativo.",
@@ -1626,4 +1639,4 @@
"setVariable": "Parâmetros de configuração",
"userId": "ID do usuário",
"userName": "Usuário"
}
}

View File

@@ -310,7 +310,9 @@
"ChatAI": "Chat AI",
"ChatHello": "Здравствуйте! Чем я могу вам помочь?",
"ChdirHelpText": "По умолчанию каталогом выполнения является домашний каталог пользователя",
"Check": "Проверять",
"CheckAssetsAmount": "Проверка количества активов",
"CheckResponse": "Проверить ответ",
"CheckViewAcceptor": "Нажмите, чтобы просмотреть одобряющего",
"CleanHelpText": "Периодическая очистка задач будет выполняться каждый день в 2 часа ночи, данные после очистки невозможно восстановить.",
"Cleaning": "Регулярная очистка",
@@ -320,6 +322,7 @@
"ClearSecret": "Очистить секрет",
"ClearSelection": "Очистить выбор",
"ClearSuccessMsg": "Очистка выполнена успешно",
"Click": "Кликнуть",
"ClickCopy": "Нажмите для копирования",
"ClientCertificate": "Сертификат клиента",
"Clipboard": "Буфер обмена",
@@ -814,6 +817,7 @@
"LoginTitleTip": "Примечание: будет отображаться на странице входа SSH пользователей корпоративной версии KoKo (например: Добро пожаловать в JumpServer)",
"LoginUserRanking": "Рейтинг входящих пользователей",
"LoginUserToday": "Входившие сегодня пользователи",
"LoginUsername": "Имя пользователя для входа",
"LoginUsers": "Активные УЗ",
"LogoIndexTip": "Примечание: будет отображена в верхнем левом углу страницы управления (рекомендуемый размер изображения: 185px*55px)",
"LogoLogoutTip": "Примечание: будет отображаться на веб-терминале пользователей корпоративной версии (рекомендуемый размер изображения: 82px*82px)",
@@ -1157,6 +1161,9 @@
"ResolveSelected": "Решение установлено",
"Resource": "Ресурсы",
"ResourceType": "Тип ресурса",
"Response": "Тело ответа",
"ResponseExpression": "Выражение тела ответа",
"ResponseExpressionTip": "Для тела ответа {count: 1} выражение можно записать как count=1 для совпадения\nДля тела ответа {data: [a, b]} выражение можно записать как data.length=2 для совпадения\nДля тела ответа {data: [{username: admin}]} выражение можно записать как data.0.username=admin для совпадения",
"RestoreButton": "Восстановить",
"RestoreDefault": "Сброс на настройки по умолчанию",
"RestoreDialogMessage": "Вы уверены, что хотите восстановить настройки по умолчанию?",
@@ -1259,6 +1266,7 @@
"Selected": "Выбрано",
"Selection": "Выбор",
"Selector": "Селектор",
"SelectorPlaceholder": "Введите локатор элемента (поддерживает CSS/XPath)",
"Send": "Отправить",
"SendVerificationCode": "Отправить код подтверждения",
"SerialNumber": "Серийный номер",
@@ -1308,6 +1316,7 @@
"Skipped": "Пропущено",
"Slack": "Slack",
"SlackOAuth": "Slack OAuth",
"Sleep": "Ждать",
"Source": "Источник",
"SourceIP": "Исходный адрес",
"SourcePort": "Исходный порт",
@@ -1321,6 +1330,7 @@
"State": "Состояние",
"StateClosed": "Закрыто",
"Status": "Статус",
"StatusCode": "Код статуса",
"StatusGreen": "Недавнее состояние - хорошее",
"StatusRed": "Не удалось выполнить последнюю задачу",
"StatusYellow": "Недавние неудачные выполнения",
@@ -1559,6 +1569,7 @@
"UsersAndUserGroups": "Пользователи/группы",
"UsersTotal": "Всего пользователей",
"Valid": "Действительно",
"Value": "Значение",
"Variable": "Переменная",
"VariableHelpText": "Вы можете использовать {{ key }} в команде для чтения встроенных переменных",
"VariableName": "Имя переменной",
@@ -1583,6 +1594,8 @@
"VisitTimeDistribution": "Посещение во временные интервалы",
"Visits": "Посещения",
"Volcengine": "Volcengine",
"WaitForSelector": "Ждать элемента",
"WaitForURL": "Ждать URL",
"Warning": "Предупреждение",
"Watermark": "Водяной знак",
"WatermarkVariableHelpText": "Вы можете использовать ${key} для чтения встроенных переменных в содержимом водяного знака",
@@ -1627,4 +1640,4 @@
"setVariable": "Задать переменную",
"userId": "ID пользователя",
"userName": "Имя пользовател"
}
}

View File

@@ -309,7 +309,9 @@
"ChatAI": "智能问答",
"ChatHello": "你好!我能为你提供什么帮助?",
"ChdirHelpText": "默认执行目录为执行用户的 home 目录",
"Check": "检查",
"CheckAssetsAmount": "校对资产数量",
"CheckResponse": "检查响应体",
"CheckViewAcceptor": "点击查看受理人",
"CleanHelpText": "定期清理任务会在 每天凌晨 2 点执行, 清理后的数据将无法恢复",
"Cleaning": "定期清理",
@@ -319,6 +321,7 @@
"ClearSecret": "清除密文",
"ClearSelection": "清空选择",
"ClearSuccessMsg": "清除成功",
"Click": "点击",
"ClickCopy": "点击复制",
"ClientCertificate": "客户端证书",
"Clipboard": "剪贴板",
@@ -574,6 +577,7 @@
"Exclude": "不包含",
"ExcludeAsset": "跳过的资产",
"ExcludeSymbol": "排除字符",
"ExcludeAccount": "排除账号",
"ExecCloudSyncErrorMsg": "云账号配置不完整,请更新后重试",
"Execute": "执行",
"ExecuteAfterSaving": "保存后执行",
@@ -812,6 +816,7 @@
"LoginTitleTip": "提示:将会显示在企业版用户 SSH 登录 KoKo 登录页面eg: 欢迎使用JumpServer开源堡垒机)",
"LoginUserRanking": "登录账号排名",
"LoginUserToday": "今日登录用户数",
"LoginUsername": "登录用户名",
"LoginUsers": "活跃账号",
"LogoIndexTip": "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px",
"LogoLogoutTip": "提示:将会显示在企业版用户的 Web 终端页面建议图片大小为82px*82px",
@@ -1155,6 +1160,9 @@
"ResolveSelected": "解决所选",
"Resource": "资源",
"ResourceType": "资源类型",
"Response": "响应体",
"ResponseExpression": "响应体表达式",
"ResponseExpressionTip": "响应体 {count: 1} 表达式可写为 count=1 来匹配\n响应体 {data: [a, b]} 表达式可写为 data.length=2 来匹配\n响应体 {data: [{username: admin}]} 表达式可写为 data.0.username=admin 来匹配",
"RestoreButton": "恢复默认",
"RestoreDefault": "恢复默认",
"RestoreDialogMessage": "您确定要恢复默认初始化吗?",
@@ -1257,6 +1265,7 @@
"Selected": "已选择",
"Selection": "可选择",
"Selector": "选择器",
"SelectorPlaceholder": "输入元素定位符(支持 CSS/XPath",
"Send": "发送",
"SendVerificationCode": "发送验证码",
"SerialNumber": "序列号",
@@ -1306,6 +1315,7 @@
"Skipped": "已跳过",
"Slack": "Slack",
"SlackOAuth": "Slack 认证",
"Sleep": "等待",
"Source": "来源",
"SourceIP": "源地址",
"SourcePort": "源端口",
@@ -1319,6 +1329,7 @@
"State": "状态",
"StateClosed": "已关闭",
"Status": "状态",
"StatusCode": "状态码",
"StatusGreen": "近期状态良好",
"StatusRed": "上一次任务执行失败",
"StatusYellow": "近期存在在执行失败",
@@ -1557,6 +1568,7 @@
"UsersAndUserGroups": "用户/用户组",
"UsersTotal": "用户总数",
"Valid": "有效",
"Value": "值",
"Variable": "变量",
"VariableHelpText": "您可以在命令中使用 {{ key }} 读取内置变量",
"VariableName": "变量名",
@@ -1581,6 +1593,8 @@
"VisitTimeDistribution": "访问时段分布",
"Visits": "访问量",
"Volcengine": "火山引擎",
"WaitForSelector": "等待元素",
"WaitForURL": "等待 URL",
"Warning": "警告",
"Watermark": "水印",
"WatermarkVariableHelpText": "您可以在自定义水印内容中使用 ${key} 读取内置变量",
@@ -1624,5 +1638,8 @@
"removeWarningMsg": "你确定要移除",
"setVariable": "设置参数",
"userId": "用户ID",
"userName": "用户名"
}
"userName": "用户名",
"ExportAsPDF": "导出 PDF",
"EMailReport": "发送邮件报告",
"Print": "打印"
}

View File

@@ -313,7 +313,9 @@
"ChatAI": "智慧問答",
"ChatHello": "你好!我能為你提供什麼幫助?",
"ChdirHelpText": "默認執行目錄為執行用戶的 home 目錄",
"Check": "檢查",
"CheckAssetsAmount": "校對資產數量",
"CheckResponse": "檢查回應體",
"CheckViewAcceptor": "點擊查看受理人",
"CleanHelpText": "定期清理任務會在 每天凌晨 2 點執行, 清理後的數據將無法恢復",
"Cleaning": "定期清理",
@@ -323,6 +325,7 @@
"ClearSecret": "清除密文",
"ClearSelection": "清空選擇",
"ClearSuccessMsg": "清除成功",
"Click": "點擊",
"ClickCopy": "點擊複製",
"ClientCertificate": "用戶端證書",
"Clipboard": "剪貼簿",
@@ -817,6 +820,7 @@
"LoginTitleTip": "提示:將會顯示在企業版使用者 SSH 登入 KoKo 登入頁面eg: 歡迎使用JumpServer開源堡壘機",
"LoginUserRanking": "會話用戶排名",
"LoginUserToday": "今日登入用戶數",
"LoginUsername": "登入使用者名稱",
"LoginUsers": "活躍帳號",
"LogoIndexTip": "提示:將會顯示在管理頁面左上方(建議圖片大小為: 185px*55px",
"LogoLogoutTip": "提示:將會顯示在企業版使用者的 Web 終端頁面建議圖片大小為82px*82px",
@@ -1160,6 +1164,9 @@
"ResolveSelected": "解決選定",
"Resource": "資源",
"ResourceType": "資源類型",
"Response": "回應體",
"ResponseExpression": "回應體運算式",
"ResponseExpressionTip": "回應體 {count: 1} 運算式可寫為 count=1 來比對\n 回應體 {data: [a, b]} 運算式可寫為 data.length=2 來比對\n 回應體 {data: [{username: admin}]} 運算式可寫為 data.0.username=admin 來比對",
"RestoreButton": "恢復默認",
"RestoreDefault": "恢復默認",
"RestoreDialogMessage": "您確定要恢復成預設初始化嗎?",
@@ -1262,6 +1269,7 @@
"Selected": "已選擇",
"Selection": "可選擇",
"Selector": "選擇器",
"SelectorPlaceholder": "輸入元素定位器(支援 CSS/XPath",
"Send": "發送",
"SendVerificationCode": "發送驗證碼",
"SerialNumber": "序號",
@@ -1311,6 +1319,7 @@
"Skipped": "已跳過",
"Slack": "Slack",
"SlackOAuth": "Slack 認證",
"Sleep": "等待",
"Source": "來源",
"SourceIP": "源地址",
"SourcePort": "源埠",
@@ -1324,6 +1333,7 @@
"State": "狀態",
"StateClosed": "已關閉",
"Status": "狀態",
"StatusCode": "狀態碼",
"StatusGreen": "近期狀態良好",
"StatusRed": "上一次任務執行失敗",
"StatusYellow": "近期存在在執行失敗",
@@ -1562,6 +1572,7 @@
"UsersAndUserGroups": "User/User Group",
"UsersTotal": "用戶總數",
"Valid": "有效",
"Value": "值",
"Variable": "變數",
"VariableHelpText": "您可以在命令中使用 {{ key }} 讀取內建變數",
"VariableName": "變數名",
@@ -1586,6 +1597,8 @@
"VisitTimeDistribution": "訪問時段分布",
"Visits": "訪問量",
"Volcengine": "火山引擎",
"WaitForSelector": "等待元素",
"WaitForURL": "等待 URL",
"Warning": "警告",
"Watermark": "水印",
"WatermarkVariableHelpText": "您可以在自訂水印內容中使用 ${key} 來讀取內建變數",
@@ -1630,4 +1643,4 @@
"setVariable": "設置參數",
"userId": "用戶ID",
"userName": "用戶名"
}
}

View File

@@ -1,3 +1,4 @@
from .aggregate import *
from .dashboard import IndexApi
from .health import PrometheusMetricsApi, HealthCheckView
from .search import GlobalSearchView

View File

@@ -1,6 +1,6 @@
# views.py
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
@@ -27,47 +27,49 @@ object_params = [
class ResourceDetailApi(ProxyMixin, APIView):
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
@extend_schema(
operation_id="get_resource_detail",
operation_summary="Get resource detail",
manual_parameters=object_params,
operation_description="""
summary="Get resource detail",
parameters=object_params,
description="""
Get resource detail.
{resource} is the resource name, GET /api/v1/resources/ to get full supported resource.
""", )
""",
)
def get(self, request, resource, pk=None):
return self._proxy(request, resource, pk=pk, action='retrieve')
@swagger_auto_schema(
@extend_schema(
operation_id="delete_resource",
operation_summary="Delete the resource ",
manual_parameters=object_params,
operation_description="Delete the resource, and can not be restored",
summary="Delete the resource",
parameters=object_params,
description="Delete the resource, and can not be restored",
)
def delete(self, request, resource, pk=None):
return self._proxy(request, resource, pk, action='destroy')
@swagger_auto_schema(
@extend_schema(
operation_id="update_resource",
operation_summary="Update the resource property",
manual_parameters=object_params,
operation_description="""
summary="Update the resource property",
parameters=object_params,
description="""
Update the resource property, all property will be update,
{resource} is the resource name, GET /api/v1/resources/ to get full supported resource.
OPTION /api/v1/resources/{resource}/{id}/?action=put to get field type and helptext.
""")
""",
)
def put(self, request, resource, pk=None):
return self._proxy(request, resource, pk, action='update')
@swagger_auto_schema(
@extend_schema(
operation_id="partial_update_resource",
operation_summary="Update the resource property",
manual_parameters=object_params,
operation_description="""
summary="Update the resource property",
parameters=object_params,
description="""
Partial update the resource property, only request property will be update,
OPTION /api/v1/resources/{resource}/{id}/?action=patch to get field type and helptext.
""")
""",
)
def patch(self, request, resource, pk=None):
return self._proxy(request, resource, pk, action='partial_update')

View File

@@ -1,7 +1,6 @@
# views.py
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework.routers import DefaultRouter
from rest_framework.views import APIView
@@ -51,32 +50,34 @@ list_schema = {
}
}
list_response = openapi.Response("Resource list response", schema=openapi.Schema(**list_schema))
from drf_spectacular.openapi import OpenApiResponse, OpenApiExample
class ResourceListApi(ProxyMixin, APIView):
@swagger_auto_schema(
@extend_schema(
operation_id="get_resource_list",
operation_summary="Get resource list",
manual_parameters=list_params,
responses={200: list_response},
operation_description="""
summary="Get resource list",
parameters=list_params,
responses={200: OpenApiResponse(description="Resource list response")},
description="""
Get resource list, you should set the resource name in the url.
OPTIONS /api/v1/resources/{resource}/?action=get to get every type resource's field type and help text.
""", )
""",
)
# ↓↓↓ Swagger 自动文档 ↓↓↓
def get(self, request, resource):
return self._proxy(request, resource)
@swagger_auto_schema(
@extend_schema(
operation_id="create_resource_by_type",
operation_summary="Create resource",
manual_parameters=create_params,
operation_description="""
summary="Create resource",
parameters=create_params,
description="""
Create resource,
OPTIONS /api/v1/resources/{resource}/?action=post to get every resource type field type and helptext, and
you will know how to create it.
""")
""",
)
def post(self, request, resource, pk=None):
if not resource:
resource = request.data.pop('resource', '')

View File

@@ -1,6 +1,6 @@
# views.py
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@@ -25,10 +25,10 @@ class ResourceTypeResourceSerializer(serializers.Serializer):
class ResourceTypeListApi(APIView):
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
@extend_schema(
operation_id="get_supported_resources",
operation_summary="Get-all-support-resources",
operation_description="Get all support resources, name, path, verbose_name description",
summary="Get-all-support-resources",
description="Get all support resources, name, path, verbose_name description",
responses={200: ResourceTypeResourceSerializer(many=True)}, # Specify the response serializer
)
def get(self, request):

View File

@@ -6,7 +6,7 @@ from typing import Dict
from django.urls import URLPattern
from django.urls import URLResolver
from drf_yasg import openapi
from drf_spectacular.utils import OpenApiParameter
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
@@ -114,8 +114,8 @@ def extract_resource_paths(urlpatterns, prefix='/api/v1/') -> Dict[str, Dict[str
def param_dic_to_param(d):
return openapi.Parameter(
d['name'], d['in'],
return OpenApiParameter(
name=d['name'], location=d['in'],
description=d['description'], type=d['type'], required=d.get('required', False)
)

View File

@@ -0,0 +1,85 @@
import re
from django.contrib.postgres.search import SearchVector
from rest_framework.views import APIView
from rest_framework.response import Response
from django.conf import settings
from django.db.models import Q
from rest_framework.permissions import IsAuthenticated
class GlobalSearchView(APIView):
limits = 5
permission_classes = [IsAuthenticated]
def get_models(self):
from users.models import User, UserGroup
from assets.models import Asset
from accounts.models import Account
from perms.models import AssetPermission
return [
[User, ['name', 'username']],
[UserGroup, ['name', 'comment']],
[Asset, ['name', 'address']],
[Account, ['name', 'username']],
[AssetPermission, ['name', 'comment']],
]
def search_model(self, model, fields, keyword):
queryset = model.objects.all()
if hasattr(model, 'get_queryset'):
queryset = model.get_queryset()
if settings.DB_ENGINE == 'postgres':
qs = model.objects.annotate(
search=SearchVector(*fields),
).filter(search=keyword)
else:
q = Q()
for field in fields:
q |= Q(**{field + '__icontains': keyword})
qs = queryset.filter(q)
return qs[:self.limits]
def get_result(self, model, fields, item, keyword):
d = {
"id": item.id, "name": item.name,
"model": model.__name__, "model_label": model._meta.verbose_name,
}
content = ""
value_list = [item.name]
for field in fields:
field_label = model._meta.get_field(field).verbose_name
value = getattr(item, field)
if value in value_list:
continue
value_list.append(value)
if content:
continue
content += f"{field_label}: {value} "
display = str(item).replace(item.name, '').replace('(', '').replace(')', '')
if display not in value:
content += f" {display} "
d["content"] = content
return d
def get(self, request):
q = request.query_params.get("q", "").strip()
models = self.get_models()
results = []
for model, fields in models:
perm = model._meta.app_label + '.' + 'view_' + model._meta.model_name
if not request.user.has_perm(perm):
continue
qs = self.search_model(model, fields, q)
for item in qs:
d = self.get_result(model, fields, item, q)
results.append(d)
return Response(results)

View File

@@ -693,6 +693,7 @@ class Config(dict):
# API 分页
'MAX_LIMIT_PER_PAGE': 10000,
'DEFAULT_PAGE_SIZE': 10,
'LIMIT_SUPER_PRIV': False,

View File

@@ -15,6 +15,15 @@ class MaxLimitOffsetPagination(LimitOffsetPagination):
def paginate_queryset(self, queryset, request, view=None):
if view and hasattr(view, 'page_max_limit'):
self.max_limit = view.page_max_limit
# 自定义的 api view就默认不约束分页了
if getattr(view, 'action') != 'list' and not getattr(view, 'default_limit'):
self.default_limit = None
if view and hasattr(view, 'page_default_limit'):
self.default_limit = view.page_default_limit
if view and hasattr(view, 'default_limit'):
self.default_limit = view.default_limit
return super().paginate_queryset(queryset, request, view)

View File

@@ -87,7 +87,7 @@ ALLOWED_DOMAINS.extend(DEBUG_HOST_PORTS)
# for host in ALLOWED_DOMAINS:
# print(' - ' + host.lstrip('.'))
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['*']
# https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS
CSRF_TRUSTED_ORIGINS = []
@@ -138,7 +138,8 @@ INSTALLED_APPS = [
'labels.apps.LabelsConfig',
'reports.apps.ReportsConfig',
'rest_framework',
'drf_yasg',
'drf_spectacular',
'drf_spectacular_sidecar',
'django_cas_ng',
'channels',
'django_filters',

View File

@@ -225,7 +225,7 @@ SESSION_RSA_PUBLIC_KEY_NAME = 'jms_public_key'
OPERATE_LOG_ELASTICSEARCH_CONFIG = CONFIG.OPERATE_LOG_ELASTICSEARCH_CONFIG
MAX_LIMIT_PER_PAGE = CONFIG.MAX_LIMIT_PER_PAGE
DEFAULT_PAGE_SIZE = CONFIG.MAX_LIMIT_PER_PAGE
DEFAULT_PAGE_SIZE = CONFIG.DEAFULT_LIMIT_PER_PAGE
PERM_TREE_REGEN_INTERVAL = CONFIG.PERM_TREE_REGEN_INTERVAL
# Magnus DB Port

View File

@@ -49,21 +49,44 @@ REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'jumpserver.rewriting.pagination.MaxLimitOffsetPagination',
'PAGE_SIZE': CONFIG.DEFAULT_PAGE_SIZE,
'EXCEPTION_HANDLER': 'common.drf.exc_handlers.common_exception_handler',
'DEFAULT_SCHEMA_CLASS': 'jumpserver.views.schema.CustomAutoSchema',
}
SWAGGER_SETTINGS = {
'DEFAULT_AUTO_SCHEMA_CLASS': 'jumpserver.views.swagger.CustomSwaggerAutoSchema',
'USE_SESSION_AUTH': True,
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
SPECTACULAR_SETTINGS = {
'TITLE': 'JumpServer API Docs',
'DESCRIPTION': 'JumpServer Restful api docs',
'VERSION': 'v1',
'LICENSE': {
'name': 'GPLv3 License',
'url': 'https://www.gnu.org/licenses/gpl-3.0.html',
},
'DEFAULT_INFO': 'jumpserver.views.swagger.api_info',
'CONTACT': {
'name': 'JumpServer',
'url': 'https://jumpserver.org',
'email': 'support@jumpserver.org',
},
"SERVE_INCLUDE_SCHEMA": False,
'SERVE_PUBLIC': True,
'BASE_PATH': '/api/v1/',
'SCHEMA_PATH_PREFIX': '/api/v1/',
'SWAGGER_UI_DIST': 'SIDECAR',
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
'SWAGGER_UI_OAUTH2_REDIRECT_URL': 'SIDECAR',
'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAuthenticated'],
'DEFAULT_GENERATOR_CLASS': 'jumpserver.views.schema.CustomSchemaGenerator',
'SWAGGER_UI_SETTINGS': {
'persistAuthorization': True,
'displayOperationId': True,
},
# 添加自定义字段扩展
'SERIALIZER_EXTENSIONS': [
'jumpserver.views.schema.ObjectRelatedFieldExtension',
'jumpserver.views.schema.LabeledChoiceFieldExtension',
'jumpserver.views.schema.BitChoicesFieldExtension',
'jumpserver.views.schema.LabelRelatedFieldExtension',
],
'SECURITY': [{'Bearer': []}],
}
# Captcha settings, more see https://django-simple-captcha.readthedocs.io/en/latest/advanced.html
CAPTCHA_IMAGE_SIZE = (180, 38)
CAPTCHA_FOREGROUND_COLOR = '#001100'

View File

@@ -38,6 +38,7 @@ api_v1 = resource_api + [
path('resources/', api.ResourceTypeListApi.as_view(), name='resource-list'),
path('resources/<str:resource>/', api.ResourceListApi.as_view()),
path('resources/<str:resource>/<str:pk>/', api.ResourceDetailApi.as_view()),
path('search/', api.GlobalSearchView.as_view()),
]
app_view_patterns = [
@@ -94,9 +95,10 @@ cache_kwargs = {
}
# docs 路由
urlpatterns += [
path('api/swagger.<format>', views.get_swagger_view().without_ui(**cache_kwargs), name='schema-json'),
re_path('api/docs/?', views.get_swagger_view().with_ui('swagger', **cache_kwargs), name="docs"),
re_path('api/redoc/?', views.get_swagger_view().with_ui('redoc', **cache_kwargs), name='redoc'),
path('api/swagger.json', views.get_swagger_view(ui='json', **cache_kwargs), name='schema-json'),
path('api/swagger.yaml', views.get_swagger_view(ui='yaml', **cache_kwargs), name='schema'),
re_path('api/docs/?', views.get_swagger_view(ui='swagger', **cache_kwargs), name="docs"),
re_path('api/redoc/?', views.get_swagger_view(ui='redoc', **cache_kwargs), name='redoc'),
]
if os.environ.get('DEBUG_TOOLBAR', False):

View File

@@ -0,0 +1,421 @@
import re
from drf_spectacular.openapi import AutoSchema
from drf_spectacular.generators import SchemaGenerator
class CustomSchemaGenerator(SchemaGenerator):
from_mcp = False
def get_schema(self, request=None, public=False):
self.from_mcp = request.query_params.get('mcp') or request.path.endswith('swagger.json')
return super().get_schema(request, public)
class CustomAutoSchema(AutoSchema):
def __init__(self, *args, **kwargs):
self.from_mcp = kwargs.get('from_mcp', False)
super().__init__(*args, **kwargs)
def map_parsers(self):
return ['application/json']
def map_renderers(self, *args, **kwargs):
return ['application/json']
def get_tags(self):
operation_keys = self._tokenize_path()
if len(operation_keys) == 1:
return []
tags = ['_'.join(operation_keys[:2])]
return tags
def get_operation(self, path, *args, **kwargs):
if path.endswith('render-to-json/'):
return None
# if not path.startswith('/api/v1/users'):
# return None
operation = super().get_operation(path, *args, **kwargs)
if not operation:
return operation
if not operation.get('summary', ''):
operation['summary'] = operation.get('operationId')
return operation
def get_operation_id(self):
tokenized_path = self._tokenize_path()
# replace dashes as they can be problematic later in code generation
tokenized_path = [t.replace('-', '_') for t in tokenized_path]
action = ''
if hasattr(self.view, 'action'):
action = self.view.action
if not action:
if self.method == 'GET' and self._is_list_view():
action = 'list'
else:
action = self.method_mapping[self.method.lower()]
if action == "bulk_destroy":
action = "bulk_delete"
if not tokenized_path:
tokenized_path.append('root')
if re.search(r'<drf_format_suffix\w*:\w+>', self.path_regex):
tokenized_path.append('formatted')
return '_'.join(tokenized_path + [action])
def get_filter_parameters(self):
if not self.should_filter():
return []
fields = []
if hasattr(self.view, 'get_filter_backends'):
backends = self.view.get_filter_backends()
elif hasattr(self.view, 'filter_backends'):
backends = self.view.filter_backends
else:
backends = []
for filter_backend in backends:
fields += self.probe_inspectors(
self.filter_inspectors, 'get_filter_parameters', filter_backend()
) or []
return fields
def get_auth(self):
return [{'Bearer': []}]
def get_operation_security(self):
"""
重写操作安全配置,统一使用 Bearer token
"""
return [{'Bearer': []}]
def get_components_security_schemes(self):
"""
重写安全方案定义,避免认证类解析错误
"""
return {
'Bearer': {
'type': 'http',
'scheme': 'bearer',
'bearerFormat': 'JWT',
'description': 'JWT token for API authentication'
}
}
@staticmethod
def exclude_some_paths(path):
# 这里可以对 paths 进行处理
excludes = [
'/report/', '/render-to-json/', '/suggestions/',
'executions', 'automations', 'change-secret-records',
'change-secret-dashboard', '/copy-to-assets/',
'/move-to-assets/', 'dashboard', 'index', 'countries',
'/resources/cache/', 'profile/mfa', 'profile/password',
'profile/permissions', 'prometheus', 'constraints'
]
for p in excludes:
if path.find(p) >= 0:
return True
return False
def exclude_some_app_model(self, path):
parts = path.split('/')
if len(parts) < 5:
return False
apps = []
if self.from_mcp:
apps = [
'ops', 'tickets', 'authentication',
'settings', 'xpack', 'terminal', 'rbac',
'notifications', 'promethues', 'acls'
]
app_name = parts[3]
if app_name in apps:
return True
models = []
model = parts[4]
if self.from_mcp:
models = [
'users', 'user-groups', 'users-groups-relations', 'assets', 'hosts', 'devices', 'databases',
'webs', 'clouds', 'gpts', 'ds', 'customs', 'platforms', 'nodes', 'zones', 'gateways',
'protocol-settings', 'labels', 'virtual-accounts', 'gathered-accounts', 'account-templates',
'account-template-secrets', 'account-backups', 'account-backup-executions',
'change-secret-automations', 'change-secret-executions', 'change-secret-records',
'gather-account-automations', 'gather-account-executions', 'push-account-automations',
'push-account-executions', 'push-account-records', 'check-account-automations',
'check-account-executions', 'account-risks', 'integration-apps', 'asset-permissions',
'asset-permissions-users-relations', 'asset-permissions-user-groups-relations',
'asset-permissions-assets-relations', 'asset-permissions-nodes-relations', 'terminal-status',
'terminals', 'tasks', 'status', 'replay-storages', 'command-storages', 'session-sharing-records',
'endpoints', 'endpoint-rules', 'applets', 'applet-hosts', 'applet-publications',
'applet-host-deployments', 'virtual-apps', 'app-providers', 'virtual-app-publications',
'celery-period-tasks', 'task-executions', 'adhocs', 'playbooks', 'variables', 'ftp-logs',
'login-logs', 'operate-logs', 'password-change-logs', 'job-logs', 'jobs', 'user-sessions',
'service-access-logs', 'chatai-prompts', 'super-connection-tokens', 'flows',
'apply-assets', 'apply-nodes', 'login-acls', 'login-asset-acls', 'command-filter-acls',
'command-groups', 'connect-method-acls', 'system-msg-subscriptions', 'roles', 'role-bindings',
'system-roles', 'system-role-bindings', 'org-roles', 'org-role-bindings', 'content-types',
'labeled-resources', 'account-backup-plans', 'account-check-engines', 'account-secrets',
'change-secret', 'integration-applications', 'push-account', 'directories', 'connection-token',
'groups', 'accounts', 'resource-types', 'favorite-assets', 'activities', 'platform-automation-methods',
]
if model in models:
return True
return False
def is_excluded(self):
if self.exclude_some_paths(self.path):
return True
if self.exclude_some_app_model(self.path):
return True
return False
def get_operation(self, path, *args, **kwargs):
operation = super().get_operation(path, *args, **kwargs)
if not operation:
return operation
operation_id = operation.get('operationId')
if 'bulk' in operation_id:
return None
if not operation.get('summary', ''):
operation['summary'] = operation.get('operationId')
exclude_operations = [
'orgs_orgs_read', 'orgs_orgs_update', 'orgs_orgs_delete',
'orgs_orgs_create', 'orgs_orgs_partial_update',
]
if operation_id in exclude_operations:
return None
return operation
# 添加自定义字段的 OpenAPI 扩展
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
from drf_spectacular.openapi import AutoSchema
from drf_spectacular.plumbing import build_basic_type
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField, BitChoicesField
class ObjectRelatedFieldExtension(OpenApiSerializerFieldExtension):
"""
为 ObjectRelatedField 提供 OpenAPI schema
"""
target_class = ObjectRelatedField
def map_serializer_field(self, auto_schema, direction):
field = self.target
# 获取字段的基本信息
field_type = 'array' if field.many else 'object'
if field_type == 'array':
# 如果是多对多关系
return {
'type': 'array',
'items': self._get_openapi_item_schema(field),
'description': getattr(field, 'help_text', ''),
'title': getattr(field, 'label', ''),
}
else:
# 如果是一对一关系
return {
'type': 'object',
'properties': self._get_openapi_properties_schema(field),
'description': getattr(field, 'help_text', ''),
'title': getattr(field, 'label', ''),
}
def _get_openapi_item_schema(self, field):
"""
获取数组项的 OpenAPI schema
"""
return self._get_openapi_object_schema(field)
def _get_openapi_object_schema(self, field):
"""
获取对象的 OpenAPI schema
"""
properties = {}
# 动态分析 attrs 中的属性类型
for attr in field.attrs:
# 尝试从 queryset 的 model 中获取字段信息
field_type = self._infer_field_type(field, attr)
properties[attr] = {
'type': field_type,
'description': f'{attr} field'
}
return {
'type': 'object',
'properties': properties,
'required': ['id'] if 'id' in field.attrs else []
}
def _infer_field_type(self, field, attr_name):
"""
智能推断字段类型
"""
try:
# 如果有 queryset尝试从 model 中获取字段信息
if hasattr(field, 'queryset') and field.queryset is not None:
model = field.queryset.model
if hasattr(model, '_meta') and hasattr(model._meta, 'fields'):
model_field = model._meta.get_field(attr_name)
if model_field:
return self._map_django_field_type(model_field)
except Exception:
pass
# 如果没有 queryset 或无法获取字段信息,使用启发式规则
return self._heuristic_field_type(attr_name)
def _map_django_field_type(self, model_field):
"""
将 Django 字段类型映射到 OpenAPI 类型
"""
field_type = type(model_field).__name__
# 整数类型
if 'Integer' in field_type or 'BigInteger' in field_type or 'SmallInteger' in field_type:
return 'integer'
# 浮点数类型
elif 'Float' in field_type or 'Decimal' in field_type:
return 'number'
# 布尔类型
elif 'Boolean' in field_type:
return 'boolean'
# 日期时间类型
elif 'DateTime' in field_type or 'Date' in field_type or 'Time' in field_type:
return 'string'
# 文件类型
elif 'File' in field_type or 'Image' in field_type:
return 'string'
# 其他类型默认为字符串
else:
return 'string'
def _heuristic_field_type(self, attr_name):
"""
启发式推断字段类型
"""
# 基于属性名的启发式规则
if attr_name in ['is_active', 'enabled', 'visible'] or attr_name.startswith('is_'):
return 'boolean'
elif attr_name in ['count', 'number', 'size', 'amount']:
return 'integer'
elif attr_name in ['price', 'rate', 'percentage']:
return 'number'
else:
# 默认返回字符串类型
return 'string'
def _get_openapi_properties_schema(self, field):
"""
获取对象属性的 OpenAPI schema
"""
return self._get_openapi_object_schema(field)['properties']
class LabeledChoiceFieldExtension(OpenApiSerializerFieldExtension):
"""
为 LabeledChoiceField 提供 OpenAPI schema
"""
target_class = LabeledChoiceField
def map_serializer_field(self, auto_schema, direction):
field = self.target
if getattr(field, 'many', False):
return {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'value': {'type': 'string'},
'label': {'type': 'string'}
}
},
'description': getattr(field, 'help_text', ''),
'title': getattr(field, 'label', ''),
}
else:
return {
'type': 'object',
'properties': {
'value': {'type': 'string'},
'label': {'type': 'string'}
},
'description': getattr(field, 'help_text', ''),
'title': getattr(field, 'label', ''),
}
class BitChoicesFieldExtension(OpenApiSerializerFieldExtension):
"""
为 BitChoicesField 提供 OpenAPI schema
"""
target_class = BitChoicesField
def map_serializer_field(self, auto_schema, direction):
field = self.target
return {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'value': {'type': 'string'},
'label': {'type': 'string'}
}
},
'description': getattr(field, 'help_text', ''),
'title': getattr(field, 'label', ''),
}
class LabelRelatedFieldExtension(OpenApiSerializerFieldExtension):
"""
为 LabelRelatedField 提供 OpenAPI schema
"""
target_class = 'common.serializers.fields.LabelRelatedField'
def map_serializer_field(self, auto_schema, direction):
field = self.target
# LabelRelatedField 返回一个包含 id, name, value, color 的对象
return {
'type': 'object',
'properties': {
'id': {
'type': 'string',
'description': 'Label ID'
},
'name': {
'type': 'string',
'description': 'Label name'
},
'value': {
'type': 'string',
'description': 'Label value'
},
'color': {
'type': 'string',
'description': 'Label color'
}
},
'required': ['id', 'name', 'value'],
'description': getattr(field, 'help_text', 'Label information'),
'title': getattr(field, 'label', 'Label'),
}

View File

@@ -1,176 +1,57 @@
import os
from drf_yasg import openapi
from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg.views import get_schema_view
from rest_framework import permissions
class CustomSchemaGenerator(OpenAPISchemaGenerator):
from_mcp = False
def get_schema(self, request=None, public=False):
self.from_mcp = request.query_params.get('mcp') or request.path.endswith('swagger.json')
return super().get_schema(request, public)
@staticmethod
def exclude_some_paths(path):
# 这里可以对 paths 进行处理
excludes = [
'/report/', '/render-to-json/', '/suggestions/',
'executions', 'automations', 'change-secret-records',
'change-secret-dashboard', '/copy-to-assets/',
'/move-to-assets/', 'dashboard', 'index', 'countries',
'/resources/cache/', 'profile/mfa', 'profile/password',
'profile/permissions', 'prometheus', 'constraints'
]
for p in excludes:
if path.find(p) >= 0:
return True
return False
def exclude_some_app_model(self, path):
parts = path.split('/')
if len(parts) < 5:
return False
apps = []
if self.from_mcp:
apps = [
'ops', 'tickets', 'authentication',
'settings', 'xpack', 'terminal', 'rbac',
'notifications', 'promethues', 'acls'
]
app_name = parts[3]
if app_name in apps:
return True
models = []
model = parts[4]
if self.from_mcp:
models = [
'users', 'user-groups', 'users-groups-relations', 'assets', 'hosts', 'devices', 'databases',
'webs', 'clouds', 'gpts', 'ds', 'customs', 'platforms', 'nodes', 'zones', 'gateways',
'protocol-settings', 'labels', 'virtual-accounts', 'gathered-accounts', 'account-templates',
'account-template-secrets', 'account-backups', 'account-backup-executions',
'change-secret-automations', 'change-secret-executions', 'change-secret-records',
'gather-account-automations', 'gather-account-executions', 'push-account-automations',
'push-account-executions', 'push-account-records', 'check-account-automations',
'check-account-executions', 'account-risks', 'integration-apps', 'asset-permissions',
'asset-permissions-users-relations', 'asset-permissions-user-groups-relations',
'asset-permissions-assets-relations', 'asset-permissions-nodes-relations', 'terminal-status',
'terminals', 'tasks', 'status', 'replay-storages', 'command-storages', 'session-sharing-records',
'endpoints', 'endpoint-rules', 'applets', 'applet-hosts', 'applet-publications',
'applet-host-deployments', 'virtual-apps', 'app-providers', 'virtual-app-publications',
'celery-period-tasks', 'task-executions', 'adhocs', 'playbooks', 'variables', 'ftp-logs',
'login-logs', 'operate-logs', 'password-change-logs', 'job-logs', 'jobs', 'user-sessions',
'service-access-logs', 'chatai-prompts', 'super-connection-tokens', 'flows',
'apply-assets', 'apply-nodes', 'login-acls', 'login-asset-acls', 'command-filter-acls',
'command-groups', 'connect-method-acls', 'system-msg-subscriptions', 'roles', 'role-bindings',
'system-roles', 'system-role-bindings', 'org-roles', 'org-role-bindings', 'content-types',
'labeled-resources', 'account-backup-plans', 'account-check-engines', 'account-secrets',
'change-secret', 'integration-applications', 'push-account', 'directories', 'connection-token',
'groups', 'accounts', 'resource-types', 'favorite-assets', 'activities', 'platform-automation-methods',
]
if model in models:
return True
return False
def get_operation(self, view, path, prefix, method, components, request):
# 这里可以对 path 进行处理
if self.exclude_some_paths(path):
return None
if self.exclude_some_app_model(path):
return None
operation = super().get_operation(view, path, prefix, method, components, request)
operation_id = operation.get('operationId')
if 'bulk' in operation_id:
return None
exclude_operations = [
'orgs_orgs_read', 'orgs_orgs_update', 'orgs_orgs_delete', 'orgs_orgs_create',
'orgs_orgs_partial_update',
]
if operation_id in exclude_operations:
return None
return operation
class CustomSwaggerAutoSchema(SwaggerAutoSchema):
def get_tags(self, operation_keys):
if len(operation_keys) > 2:
return [operation_keys[0] + '_' + operation_keys[1]]
return super().get_tags(operation_keys)
def get_operation_id(self, operation_keys):
action = ''
dump_keys = [k for k in operation_keys]
if hasattr(self.view, 'action'):
action = self.view.action
if action == "bulk_destroy":
action = "bulk_delete"
if dump_keys[-2] == "children":
if self.path.find('id') < 0:
dump_keys.insert(-2, "root")
if dump_keys[0] == "perms" and dump_keys[1] == "users":
if self.path.find('{id}') < 0:
dump_keys.insert(2, "my")
if action.replace('bulk_', '') == dump_keys[-1]:
dump_keys[-1] = action
return super().get_operation_id(tuple(dump_keys))
def get_operation(self, operation_keys):
operation = super().get_operation(operation_keys)
if not getattr(operation, 'summary', ''):
operation.summary = operation.operation_id
return operation
def get_filter_parameters(self):
if not self.should_filter():
return []
fields = []
if hasattr(self.view, 'get_filter_backends'):
backends = self.view.get_filter_backends()
elif hasattr(self.view, 'filter_backends'):
backends = self.view.filter_backends
else:
backends = []
for filter_backend in backends:
fields += self.probe_inspectors(self.filter_inspectors, 'get_filter_parameters', filter_backend()) or []
return fields
api_info = openapi.Info(
title="JumpServer API Docs",
default_version='v1',
description="JumpServer Restful api docs",
terms_of_service="https://www.jumpserver.com",
contact=openapi.Contact(email="support@lxware.hk"),
license=openapi.License(name="GPLv3 License"),
from drf_spectacular.views import (
SpectacularSwaggerView, SpectacularRedocView,
SpectacularYAMLAPIView, SpectacularJSONAPIView
)
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from rest_framework.response import Response
from django.contrib.auth.mixins import LoginRequiredMixin
def get_swagger_view():
from ..urls import api_v1
from django.urls import path, include
patterns = [
path('api/v1/', include(api_v1))
]
class SwaggerUI(LoginRequiredMixin, SpectacularSwaggerView):
pass
with_auth = os.environ.get('DOC_AUTH', '1') == '1'
if with_auth:
permission_classes = (permissions.IsAuthenticated,)
public = False
else:
permission_classes = []
public = True
schema_view = get_schema_view(
api_info,
public=public,
patterns=patterns,
generator_class=CustomSchemaGenerator,
permission_classes=permission_classes
)
return schema_view
class Redoc(LoginRequiredMixin, SpectacularRedocView):
pass
class SchemeMixin:
def get(self, request, *args, **kwargs):
schema = super().get(request, *args, **kwargs).data
host = request.get_host()
schema['servers'] = [
{"url": f"https://{host}", "description": "HTTPS Server"},
{"url": f"http://{host}", "description": "HTTP Server"},
]
if request.scheme == 'http':
schema['servers'] = schema['servers'][::-1]
schema['components']['securitySchemes'] = {
'Bearer': {
'type': 'http',
'scheme': 'bearer',
'bearerFormat': 'JWT',
}
}
return Response(schema)
@method_decorator(cache_page(60 * 5,), name="dispatch")
class JsonApi(SchemeMixin, SpectacularJSONAPIView):
pass
@method_decorator(cache_page(60 * 5,), name="dispatch")
class YamlApi(SchemeMixin, SpectacularYAMLAPIView):
pass
def get_swagger_view(ui=None, **kwargs):
if ui == 'swagger':
return SwaggerUI.as_view(url_name='schema')
elif ui == 'redoc':
return Redoc.as_view(url_name='schema')
elif ui == 'json':
return JsonApi.as_view()
elif ui == 'yaml':
return YamlApi.as_view()

View File

@@ -20,11 +20,11 @@ class Label(JMSOrgBaseModel):
verbose_name = _("Tag")
@lazyproperty
def res_count(self):
def res_count(self) -> int:
return self.labeled_resources.count()
@lazyproperty
def display_name(self):
def display_name(self) -> str:
return "{}:{}".format(self.name, self.value)
def __str__(self):

View File

@@ -73,5 +73,5 @@ class ContentTypeResourceSerializer(serializers.Serializer):
name = serializers.SerializerMethodField()
@staticmethod
def get_name(obj):
def get_name(obj) -> str:
return str(obj)

View File

@@ -0,0 +1,78 @@
#!/usr/bin/python
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: website_ping
short_description: Use Playwright to simulate a browser for connectivity testing
description:
- Use Playwright to simulate a browser for connectivity testing
options:
login_host:
description: The target host to connect.
type: str
required: True
login_user:
description: The username for the website connection.
type: str
required: True
login_password:
description: The password for the website connection.
type: str
required: True
no_log: True
timeout:
description: Timeout period for step execution.
type: int
required: False
steps:
description: Meta-information for browser-emulated actions.
type: list
required: False
'''
EXAMPLES = '''
- name: Ping asset server using Playwright.
website_ping:
login_host: 127.0.0.1
'''
RETURN = '''
is_available:
description: Indicate whether the target server is reachable via Playwright.
returned: always
type: bool
sample: true
'''
from ansible.module_utils.basic import AnsibleModule
from libs.ansible.modules_utils.web_common import WebAutomationHandler, common_argument_spec
def main():
options = common_argument_spec()
module = AnsibleModule(argument_spec=options, supports_check_mode=False)
extra_infos = {
'login_username': module.params['login_user'],
'login_password': module.params['login_password'],
}
handler = WebAutomationHandler(
address=module.params['login_host'],
timeout=module.params['timeout'],
load_state=module.params['load_state'],
extra_infos=extra_infos,
)
try:
handler.execute(steps=module.params['steps'])
except Exception as e:
module.fail_json(msg=str(e))
result = {'changed': False, 'is_available': True}
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,94 @@
#!/usr/bin/python
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: website_user
short_description: Use Playwright to simulate browser operations for users.
description:
- Use Playwright to simulate browser operations for users, such as password change.
options:
login_host:
description: The target host to connect.
type: str
required: True
login_user:
description: The username for the website connection.
type: str
required: True
login_password:
description: The password for the website connection.
type: str
required: True
no_log: True
name:
description: The name of the user to change password.
required: true
aliases: [user]
type: str
password:
description: The password to use for the user.
type: str
aliases: [pass]
timeout:
description: Timeout period for step execution.
type: int
required: False
steps:
description: Meta-information for browser-emulated actions.
type: list
required: False
'''
EXAMPLES = '''
- name: Change password using Playwright.
website_user:
login_host: 127.0.0.1
'''
RETURN = '''
failed:
description: Verify whether the task simulated and operated via Playwright has succeeded.
returned: always
type: bool
sample: false
'''
from ansible.module_utils.basic import AnsibleModule
from libs.ansible.modules_utils.web_common import WebAutomationHandler, common_argument_spec
def main():
argument_spec = common_argument_spec()
argument_spec.update(
name=dict(required=True, aliases=['user']),
password=dict(aliases=['pass'], no_log=True),
)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
extra_infos = {
'login_username': module.params['login_user'],
'login_password': module.params['login_password'],
'username': module.params['name'],
'password': module.params['password'],
}
handler = WebAutomationHandler(
address=module.params['login_host'],
timeout=module.params['timeout'],
load_state=module.params['load_state'],
extra_infos=extra_infos,
)
try:
handler.execute(steps=module.params['steps'])
except Exception as e:
module.fail_json(msg=str(e))
result = {'changed': True}
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,245 @@
import time
from urllib.parse import urlparse, urljoin
from playwright.sync_api import sync_playwright, TimeoutError
from ansible.module_utils._text import to_native
def common_argument_spec():
options = dict(
login_host=dict(type='str', required=False, default='localhost'),
login_user=dict(type='str', required=False),
login_password=dict(type='str', required=False, no_log=True),
steps=dict(type='list', required=False, default=[]),
load_state=dict(type='str', required=False, default='load'),
timeout=dict(type='int', required=False, default=10000),
)
return options
class WebAutomationHandler(object):
def __init__(self, address, load_state=None, timeout=10000, extra_infos=None):
self._response_mapping = {}
self._context = None
self._extra_infos = extra_infos or {}
self.address = address
self.load_state = load_state or 'load'
self.timeout = timeout
def _iframe(self, selector):
if not selector:
return
if selector.startswith(('id=', 'name=')):
key, value = selector.split('=', 1)
frame = self._context.frame(name=value)
else:
iframe_elem = self._context.wait_for_selector(selector, timeout=self.timeout)
frame = iframe_elem.content_frame()
if not frame:
raise RuntimeError(f"Iframe not found: {selector}")
frame.wait_for_selector("body", timeout=self.timeout)
self._context = frame
def _input(self, selector, value):
if not selector:
return
elem = self._context.wait_for_selector(selector, timeout=self.timeout)
elem.fill(value)
def _click(self, selector):
if not selector:
return
elem = self._context.wait_for_selector(selector, timeout=self.timeout)
elem.scroll_into_view_if_needed()
elem.click(timeout=self.timeout)
def __combine_url(self, url_path):
if not url_path:
return self.address
parsed = urlparse(url_path)
if parsed.netloc:
return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
else:
re_parsed = urlparse(urljoin(self.address, url_path))
return f"{re_parsed.scheme}://{re_parsed.netloc}{re_parsed.path}"
def _check(self, config: dict):
method = config.get('type')
if not method:
return
if method == 'wait_for_url':
url = config.get('url', '')
url = self.__combine_url(url)
self._context.wait_for_url(url, timeout=self.timeout)
elif method == 'wait_for_selector':
selector = config.get('selector', '')
self._context.wait_for_selector(selector, timeout=self.timeout)
elif method == 'check_response':
self._check_response(
method=config.get('method'),
url=config.get('url'),
status_code=config.get('status_code'),
body_expr=config.get('body_expr'),
)
@staticmethod
def __get_nested_value(data, key_path):
keys = key_path.split('.')
current = data
for key in keys:
if isinstance(current, dict):
current = current.get(key)
elif isinstance(current, list) and key.isdigit():
index = int(key)
current = current[index] if 0 <= index < len(current) else None
else:
return None
if current is None:
return None
return current
@staticmethod
def __compare_values(actual, expected):
if actual is None:
return expected.lower() in ['none', 'null', '']
if isinstance(actual, str):
return str(actual) == str(expected)
if isinstance(actual, (int, float)):
try:
return float(actual) == float(expected)
except ValueError:
return str(actual) == str(expected)
if isinstance(actual, bool):
expected_lower = expected.lower()
if expected_lower in ['true', '1', 'yes']:
return actual is True
elif expected_lower in ['false', '0', 'no']:
return actual is False
if isinstance(actual, list) and expected.startswith('length='):
expected_len = int(expected[7:])
return len(actual) == expected_len
return str(actual) == str(expected)
def __validate_response(self, resp_data, body_expr):
try:
if '=' in body_expr:
key, expected_value = body_expr.split('=', 1)
elif ':' in body_expr:
key, expected_value = body_expr.split(':', 1)
else:
return False
key = key.strip()
expected_value = expected_value.strip()
actual_value = self.__get_nested_value(resp_data, key)
return self.__compare_values(actual_value, expected_value)
except Exception: # noqa
return False
def _check_response(self, method, url, status_code, body_expr):
if not method:
return False
timeout_seconds = self.timeout / 1000
start_time = time.time()
check_url = self.__combine_url(url)
expected_status = None
if status_code:
try:
expected_status = int(status_code)
except ValueError:
expected_status = None
while time.time() - start_time < timeout_seconds:
self._context.wait_for_timeout(50)
method = method.upper()
if method not in self._response_mapping:
continue
url_mapping = self._response_mapping[method]
if len(url_mapping) == 0:
continue
url, response = url_mapping.popitem()
if expected_status and response['status'] != expected_status:
continue
if check_url and url != check_url:
continue
if body_expr and not self.__validate_response(response['data'], body_expr):
continue
return
raise TimeoutError(
f'Check response failed: method={method}, url={url}, status={status_code}'
)
def _handle_response(self, response):
if response.url.endswith(('.js', '.css')):
return
method = response.request.method.upper()
url = self.__combine_url(response.url)
self._response_mapping.setdefault(method, {})
try:
data = response.json()
except Exception: # noqa
data = {}
response_data = {'status': response.status, 'data': data}
self._response_mapping[method][url] = response_data
def execute(self, steps: list):
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
self._context = browser.new_page()
try:
self._context.on('response', self._handle_response)
self._context.goto(self.address, wait_until=self.load_state)
for step in steps:
action_type = step.get('action')
if not action_type:
continue
config = step.get('config', {})
if action_type == 'iframe':
self._iframe(config['selector'])
elif action_type == 'input':
value = self._extra_infos.get(config['value'], config['value'])
self._input(config['selector'], value)
elif action_type == 'click':
self._click(config['selector'])
elif action_type == 'check':
self._check(config)
elif action_type == 'sleep':
try:
sleep_time = float(config['value'])
except ValueError:
sleep_time = 0
self._context.wait_for_timeout(sleep_time * 1000)
else:
raise ValueError(f"Unsupported action type: {action_type}")
return True
except TimeoutError:
raise TimeoutError(f'Task execution timeout when execute step: {step}')
except Exception as e:
raise Exception(f'Execute steps failed: {to_native(e)}')
finally:
browser.close()

View File

@@ -45,7 +45,7 @@ class SystemMsgSubscription(JMSBaseModel):
self.message_type_label = msg_label
@property
def receivers(self):
def receivers(self) -> list:
from notifications.backends import BACKEND
users = [user for user in self.users.all()]

View File

@@ -6,6 +6,7 @@ from notifications.models import SystemMsgSubscription, UserMsgSubscription
class SystemMsgSubscriptionSerializer(BulkModelSerializer):
receive_backends = serializers.ListField(child=serializers.CharField())
message_type_label = serializers.CharField(read_only=True)
class Meta:
model = SystemMsgSubscription

Some files were not shown because too many files have changed in this diff Show More