mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 16:42:34 +00:00
Compare commits
35 Commits
v4.10.5-lt
...
pr@dev@fea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecdc1a056a | ||
|
|
5001ca6960 | ||
|
|
16461b0fa9 | ||
|
|
528b0ea1ba | ||
|
|
60f06adaa9 | ||
|
|
7a6187b95f | ||
|
|
aacaf3a174 | ||
|
|
3c9d2534fa | ||
|
|
4f79abe678 | ||
|
|
ae9956ff91 | ||
|
|
429677e0ce | ||
|
|
034ee65157 | ||
|
|
fdd7d9b6b1 | ||
|
|
db0e21f5d9 | ||
|
|
468b84eb3d | ||
|
|
28d5475d0f | ||
|
|
b9c60d856f | ||
|
|
bd1d73c6dd | ||
|
|
bf92c756d4 | ||
|
|
62ebe0d636 | ||
|
|
0b1fea8492 | ||
|
|
65b5f573f8 | ||
|
|
bb639e1fe7 | ||
|
|
395b868dcf | ||
|
|
1350b774b3 | ||
|
|
af7a00c1b1 | ||
|
|
965ec7007c | ||
|
|
1372fd7535 | ||
|
|
3b0ef4cca7 | ||
|
|
6832abdaad | ||
|
|
c6bf290dbb | ||
|
|
23ab66c11a | ||
|
|
1debaa5547 | ||
|
|
47413966c9 | ||
|
|
703f39607c |
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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, ):
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
36
apps/accounts/automations/change_secret/web/website/main.yml
Normal file
36
apps/accounts/automations/change_secret/web/website/main.yml
Normal 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
|
||||
@@ -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
|
||||
@@ -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 }}"
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -16,6 +16,7 @@ class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
|
||||
'types': TypeSerializer,
|
||||
}
|
||||
permission_classes = (IsValidUser,)
|
||||
default_limit = None
|
||||
|
||||
def get_queryset(self):
|
||||
return AllTypes.categories()
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
13
apps/assets/automations/ping/web/website/main.yml
Normal file
13
apps/assets/automations/ping/web/website/main.yml
Normal 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 }}"
|
||||
50
apps/assets/automations/ping/web/website/manifest.yml
Normal file
50
apps/assets/automations/ping/web/website/manifest.yml
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,))
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
备注:
|
||||
|
||||
@@ -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:*')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
"SessionLockedMessage": "此会话已被 %s 锁定,无法继续执行命令",
|
||||
"SessionUnlockedMessage": "此会话已被 %s 解锁,可以继续执行命令",
|
||||
"ShowProperties": "属性",
|
||||
"StopHotKey": "停止 (Ctrl + C)",
|
||||
"StopHotKey": "停止 (Ctrl + D)",
|
||||
"Submit": "提交",
|
||||
"Total": "总计",
|
||||
"Type": "类型",
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 "チケットの流れ"
|
||||
|
||||
|
||||
@@ -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 "작업 요청 프로세스"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 "Поток заявок"
|
||||
|
||||
|
||||
@@ -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 "工单流程"
|
||||
|
||||
|
||||
@@ -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 "工單流程"
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "ユーザー名"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "사용자명"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "Имя пользовател"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "打印"
|
||||
}
|
||||
|
||||
@@ -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": "用戶名"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .aggregate import *
|
||||
from .dashboard import IndexApi
|
||||
from .health import PrometheusMetricsApi, HealthCheckView
|
||||
from .search import GlobalSearchView
|
||||
@@ -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')
|
||||
|
||||
@@ -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', '')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
85
apps/jumpserver/api/search.py
Normal file
85
apps/jumpserver/api/search.py
Normal 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)
|
||||
@@ -693,6 +693,7 @@ class Config(dict):
|
||||
|
||||
# API 分页
|
||||
'MAX_LIMIT_PER_PAGE': 10000,
|
||||
'DEFAULT_PAGE_SIZE': 10,
|
||||
|
||||
'LIMIT_SUPER_PRIV': False,
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
421
apps/jumpserver/views/schema.py
Normal file
421
apps/jumpserver/views/schema.py
Normal 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'),
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
|
||||
@@ -73,5 +73,5 @@ class ContentTypeResourceSerializer(serializers.Serializer):
|
||||
name = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def get_name(obj):
|
||||
def get_name(obj) -> str:
|
||||
return str(obj)
|
||||
|
||||
78
apps/libs/ansible/modules/website_ping.py
Normal file
78
apps/libs/ansible/modules/website_ping.py
Normal 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()
|
||||
94
apps/libs/ansible/modules/website_user.py
Normal file
94
apps/libs/ansible/modules/website_user.py
Normal 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()
|
||||
245
apps/libs/ansible/modules_utils/web_common.py
Normal file
245
apps/libs/ansible/modules_utils/web_common.py
Normal 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()
|
||||
@@ -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()]
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user