Compare commits

..

40 Commits

Author SHA1 Message Date
ibuler
000bb100cd perf: try to decrypt then origin value 2025-08-29 11:00:19 +08:00
wangruidong
36f3071eed fix: Ensure command arguments are safely quoted in safe_run_cmd 2025-08-28 14:14:36 +08:00
老广
15259fc10c Update base.py 2025-08-21 22:05:51 +08:00
Bryan
f31994fdcd Merge pull request #15899 from jumpserver/dev 2025-08-21 19:03:18 +08:00
Bryan
71766418bb Merge pull request #15742 from jumpserver/dev
merge: v4.10.4-lts
2025-07-17 15:12:58 +08:00
Bryan
a9399dd709 Merge pull request #15608 from jumpserver/dev
v4.10.2
2025-06-19 20:14:21 +08:00
Bryan
d0cb9e5432 Merge pull request #15412 from jumpserver/dev
v4.10.0
2025-05-15 17:11:43 +08:00
老广
558188da90 merge: dev to master
Ready to relase
2025-04-17 20:24:45 +08:00
Bryan
ad5460dab8 Merge pull request #15086 from jumpserver/dev
v4.8.0
2025-03-20 18:44:44 +08:00
Bryan
4d37dca0de Merge pull request #14901 from jumpserver/dev
v4.7.0
2025-02-20 10:21:16 +08:00
Bryan
2ca4002624 Merge pull request #14813 from jumpserver/dev
v4.6.0
2025-01-15 14:38:17 +08:00
Bryan
053d640e4c Merge pull request #14699 from jumpserver/dev
v4.5.0
2024-12-19 16:04:45 +08:00
Bryan
f3acc28ded Merge pull request #14697 from jumpserver/dev
v4.5.0
2024-12-19 15:57:11 +08:00
Bryan
25987545db Merge pull request #14511 from jumpserver/dev
v4.4.0
2024-11-21 19:00:35 +08:00
Bryan
6720ecc6e0 Merge pull request #14319 from jumpserver/dev
v4.3.0
2024-10-17 14:55:38 +08:00
老广
0b3a7bb020 Merge pull request #14203 from jumpserver/dev
merge: from dev to master
2024-09-19 19:37:19 +08:00
Bryan
56373e362b Merge pull request #13988 from jumpserver/dev
v4.1.0
2024-08-16 18:40:35 +08:00
Bryan
02fc045370 Merge pull request #13600 from jumpserver/dev
v4.0.0
2024-07-03 19:04:35 +08:00
Bryan
e4ac73896f Merge pull request #13452 from jumpserver/dev
v3.10.11-lts
2024-06-19 16:01:26 +08:00
Bryan
1518f792d6 Merge pull request #13236 from jumpserver/dev
v3.10.10-lts
2024-05-16 16:04:07 +08:00
Bai
67277dd622 fix: 修复仪表盘会话排序数量都是 1 的问题 2024-04-22 19:42:33 +08:00
Bryan
82e7f020ea Merge pull request #13094 from jumpserver/dev
v3.10.9 (dev to master)
2024-04-22 19:39:53 +08:00
Bryan
f20b9e01ab Merge pull request #13062 from jumpserver/dev
v3.10.8 dev to master
2024-04-18 18:01:20 +08:00
Bryan
8cf8a3701b Merge pull request #13059 from jumpserver/dev
v3.10.8
2024-04-18 17:16:37 +08:00
Bryan
7ba24293d1 Merge pull request #12736 from jumpserver/pr@dev@master_fix
fix: 解决冲突
2024-02-29 16:38:43 +08:00
Bai
f10114c9ed fix: 解决冲突 2024-02-29 16:37:10 +08:00
Bryan
cf31cbfb07 Merge pull request #12729 from jumpserver/dev
v3.10.4
2024-02-29 16:19:59 +08:00
wangruidong
0edad24d5d fix: 资产过期消息提示发送失败 2024-02-04 11:41:48 +08:00
ibuler
1f1c1a9157 fix: 修复定时检测用户是否活跃任务无法执行的问题 2024-01-23 09:28:38 +00:00
feng
6c9d271ae1 fix: redis 密码有特殊字符celery beat启动失败 2024-01-22 06:18:34 +00:00
Bai
6ff852e225 perf: 修复 Count 时没有去重的问题 2024-01-22 06:16:25 +00:00
Bryan
baa75dc735 Merge pull request #12566 from jumpserver/master
v3.10.2
2024-01-17 07:34:28 -04:00
Bryan
8a9f0436b8 Merge pull request #12565 from jumpserver/dev
v3.10.2
2024-01-17 07:23:30 -04:00
Bryan
a9620a3cbe Merge pull request #12461 from jumpserver/master
v3.10.1
2023-12-29 11:33:05 +05:00
Bryan
769e7dc8a0 Merge pull request #12460 from jumpserver/dev
v3.10.1
2023-12-29 11:20:36 +05:00
Bryan
2a70449411 Merge pull request #12458 from jumpserver/dev
v3.10.1
2023-12-29 11:01:13 +05:00
Bryan
8df720f19e Merge pull request #12401 from jumpserver/dev
v3.10
2023-12-21 15:14:19 +05:00
老广
dabbb45f6e Merge pull request #12144 from jumpserver/dev
v3.9.0
2023-11-16 18:23:05 +08:00
Bryan
ce24c1c3fd Merge pull request #11914 from jumpserver/dev
v3.8.0
2023-10-19 03:37:39 -05:00
Bryan
3c54c82ce9 Merge pull request #11636 from jumpserver/dev
v3.7.0
2023-09-21 17:02:48 +08:00
148 changed files with 996 additions and 7645 deletions

View File

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

View File

@@ -2,7 +2,7 @@
<a name="readme-top"></a>
<a href="https://jumpserver.com" target="_blank"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
## An open-source PAM platform (Bastion Host)
## An open-source PAM tool (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) 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.
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.
<picture>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -63,10 +63,12 @@ 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

View File

@@ -113,18 +113,6 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
if host.get('error'):
return host
inventory_hosts = []
if asset.type == HostTypes.WINDOWS:
if self.secret_type == SecretType.SSH_KEY:
host['error'] = _("Windows does not support SSH key authentication")
return host
if self.secret_strategy == SecretStrategy.custom:
new_secret = self.execution.snapshot['secret']
if '>' in new_secret or '^' in new_secret:
host['error'] = _("Windows password cannot contain special characters like > ^")
return host
host['ssh_params'] = {}
accounts = self.get_accounts(account)
@@ -142,6 +130,11 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
if asset.type == HostTypes.WINDOWS:
accounts = accounts.filter(secret_type=SecretType.PASSWORD)
inventory_hosts = []
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
print(f'Windows {asset} does not support ssh key push')
return inventory_hosts
for account in accounts:
h = deepcopy(host)
h['name'] += '(' + account.username + ')' # To distinguish different accounts

View File

@@ -5,14 +5,12 @@
tasks:
- name: Test SQLServer connection
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: |
SELECT @@version
register: db_info
@@ -25,53 +23,45 @@
var: info
- name: Check whether SQLServer User exist
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
when: db_info is succeeded
register: user_exist
- name: Change SQLServer password
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
- name: Add SQLServer user
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; CREATE USER {{ account.username }} FOR LOGIN {{ account.username }}; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0
- name: Verify password
mssql_script:
community.general.mssql_script:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: |
SELECT @@version
when: check_conn_after_change

View File

@@ -9,7 +9,7 @@ from accounts.const import (
AutomationTypes, SecretStrategy, ChangeSecretRecordStatusChoice
)
from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretReportMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer
from common.utils import get_logger
from common.utils.file import encrypt_and_compress_zip_file
@@ -94,6 +94,10 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
if not recipients:
return
context = self.get_report_context()
for user in recipients:
ChangeSecretReportMsg(user, context).publish()
if not records:
return

View File

@@ -5,14 +5,12 @@
tasks:
- name: Test SQLServer connection
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: |
SELECT
l.name,

View File

@@ -5,14 +5,12 @@
tasks:
- name: Test SQLServer connection
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: |
SELECT @@version
register: db_info
@@ -25,55 +23,47 @@
var: info
- name: Check whether SQLServer User exist
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
when: db_info is succeeded
register: user_exist
- name: Change SQLServer password
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
register: change_info
- name: Add SQLServer user
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0
register: change_info
- name: Verify password
mssql_script:
community.general.mssql_script:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: |
SELECT @@version
when: check_conn_after_change

View File

@@ -5,13 +5,11 @@
tasks:
- name: "Remove account"
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: "{{ jms_asset.spec_info.db_name }}"
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "DROP LOGIN {{ account.username }}; select @@version"

View File

@@ -5,13 +5,11 @@
tasks:
- name: Verify account
mssql_script:
community.general.mssql_script:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: |
SELECT @@version

View File

@@ -1,5 +1,8 @@
# -*- 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
@@ -11,9 +14,6 @@ __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,8 +23,6 @@ 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:
@@ -34,8 +32,6 @@ class AZUREVaultClient(object):
return True, ''
def get(self, name, version=None):
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
try:
secret = self.client.get_secret(name, version)
return secret.value

View File

@@ -132,7 +132,7 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
return self.asset.platform
@lazyproperty
def alias(self) -> str:
def alias(self):
"""
别称,因为有虚拟账号,@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) -> bool:
def is_virtual(self):
"""
不要用 username 去判断,因为可能是构造的 account 对象,设置了同名账号的用户名,
"""
return self.alias.startswith('@')
def is_ds_account(self) -> bool:
def is_ds_account(self):
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) -> str:
def ds_domain(self):
"""这个不能去掉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) -> str:
def full_username(self):
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) -> bool:
def has_secret(self):
return bool(self.secret)
@lazyproperty
def versions(self) -> int:
def versions(self):
return self.history.count()
def get_su_from_accounts(self):

View File

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

View File

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

View File

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

View File

@@ -18,11 +18,11 @@ class VirtualAccount(JMSOrgBaseModel):
verbose_name = _('Virtual account')
@property
def name(self) -> str:
def name(self):
return self.get_alias_display()
@property
def username(self) -> str:
def username(self):
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) -> str:
def comment(self):
comments_map = {
AliasAccount.INPUT: _('Non-asset account, Input username/password on connect'),
AliasAccount.USER: _('The account username name same with user on connect'),

View File

@@ -253,8 +253,6 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
'source_id': {'required': False, 'allow_null': True},
}
fields_unimport_template = ['params']
# 手动判断唯一性校验
validators = []
@classmethod
def setup_eager_loading(cls, queryset):
@@ -265,21 +263,6 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
)
return queryset
def validate(self, attrs):
instance = getattr(self, "instance", None)
if instance:
return super().validate(attrs)
field_errors = {}
for _fields in Account._meta.unique_together:
lookup = {field: attrs.get(field) for field in _fields}
if Account.objects.filter(**lookup).exists():
verbose_names = ', '.join([str(Account._meta.get_field(f).verbose_name) for f in _fields])
msg_template = _('Account already exists. Field(s): {fields} must be unique.')
field_errors[_fields[0]] = msg_template.format(fields=verbose_names)
raise serializers.ValidationError(field_errors)
return attrs
class AccountDetailSerializer(AccountSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
@@ -473,8 +456,6 @@ 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 = {
@@ -489,7 +470,6 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
class AccountHistorySerializer(serializers.ModelSerializer):
secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type'))
secret = serializers.CharField(label=_('Secret'), read_only=True)
id = serializers.IntegerField(label=_('ID'), source='history_id', read_only=True)
class Meta:

View File

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

View File

@@ -130,7 +130,7 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer):
read_only_fields = fields
@staticmethod
def get_is_success(obj) -> bool:
def get_is_success(obj):
return obj.status == ChangeSecretRecordStatusChoice.success
@@ -157,7 +157,7 @@ class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
read_only_fields = fields
@staticmethod
def get_asset(instance) -> str:
def get_asset(instance):
return str(instance.asset)
@staticmethod
@@ -165,7 +165,7 @@ class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
return str(instance.account)
@staticmethod
def get_is_success(obj) -> str:
def get_is_success(obj):
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) -> dict:
def get_meta(obj):
return account_secret_task_status.get(str(obj.id))
@staticmethod
def get_ttl(obj) -> int:
def get_ttl(obj):
return account_secret_task_status.get_ttl(str(obj.id))

View File

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

View File

@@ -0,0 +1,36 @@
{% load i18n %}
<h3>{% trans 'Task name' %}: {{ name }}</h3>
<h3>{% trans 'Task execution id' %}: {{ execution_id }}</h3>
<p>{% trans 'Respectful' %} {{ recipient }}</p>
<p>{% trans 'Hello! The following is the failure of changing the password of your assets or pushing the account. Please check and handle it in time.' %}</p>
<table style="width: 100%; border-collapse: collapse; max-width: 100%; text-align: left; margin-top: 20px;">
<caption></caption>
<thead>
<tr style="background-color: #f2f2f2;">
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Asset' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Account' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Error' %}</th>
</tr>
</thead>
<tbody>
{% for asset_name, account_username, error in asset_account_errors %}
<tr>
<td style="border: 1px solid #ddd; padding: 10px;">{{ asset_name }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">{{ account_username }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">
<div style="
max-width: 90%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;"
title="{{ error }}"
>
{{ error }}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,13 +6,11 @@
tasks:
- name: Test SQLServer connection
mssql_script:
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: |
SELECT @@version

View File

@@ -250,12 +250,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
'default': False,
'label': _('Auth username')
},
'enable_cluster_mode': {
'type': 'bool',
'default': False,
'label': _('Enable cluster mode'),
'help_text': _('Enable if this Redis instance is part of a cluster')
},
}
},
}

View File

@@ -112,7 +112,7 @@ class Protocol(models.Model):
return protocols[0] if len(protocols) > 0 else {}
@property
def setting(self) -> dict:
def setting(self):
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) -> bool:
def public(self):
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) -> dict:
def spec_info(self):
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) -> dict:
def auto_config(self):
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) -> str:
def type(self):
return self.platform.type
@lazyproperty
def category(self) -> str:
def category(self):
return self.platform.category
def is_category(self, category):

View File

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

View File

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

View File

@@ -147,7 +147,6 @@ 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'))
@@ -426,18 +425,6 @@ class DetailMixin(serializers.Serializer):
gathered_info = MethodSerializer(label=_('Gathered info'), read_only=True)
auto_config = serializers.DictField(read_only=True, label=_('Auto info'))
@staticmethod
def get_auto_config(obj) -> dict:
return obj.auto_config
@staticmethod
def get_gathered_info(obj) -> dict:
return obj.gathered_info
@staticmethod
def get_spec_info(obj) -> dict:
return obj.spec_info
def get_instance(self):
request = self.context.get('request')
if not self.instance and UUID_PATTERN.findall(request.path):

View File

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

View File

@@ -1,6 +1,6 @@
import os
import uuid
from datetime import timedelta, datetime
from datetime import timedelta
from importlib import import_module
from django.conf import settings
@@ -40,7 +40,7 @@ __all__ = [
class JobLog(JobExecution):
@property
def creator_name(self) -> str:
def creator_name(self):
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) -> str:
def backend_display(self):
return gettext(self.backend)
@classmethod
@@ -258,7 +258,7 @@ class UserLoginLog(models.Model):
return login_logs
@property
def reason_display(self) -> str:
def reason_display(self):
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) -> str:
def backend_display(self):
return gettext(self.backend)
@property
def is_active(self) -> bool:
def is_active(self):
return user_session_manager.check_active(self.key)
@property
def date_expired(self) -> datetime:
def date_expired(self):
session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore
session_store = session_store_cls(session_key=self.key)
cache_key = session_store.cache_key

View File

@@ -119,11 +119,11 @@ class OperateLogSerializer(BulkOrgResourceModelSerializer):
fields = fields_small
@staticmethod
def get_resource_type(instance) -> str:
def get_resource_type(instance):
return _(instance.resource_type)
@staticmethod
def get_resource(instance) -> str:
def get_resource(instance):
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) -> str:
def get_timestamp(obj):
return as_current_tz(obj['datetime']).strftime('%Y-%m-%d %H:%M:%S')
@staticmethod
def get_content(obj) -> str:
def get_content(obj):
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) -> str:
def get_detail_url(obj):
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) -> bool:
def get_is_current_user_session(self, obj):
request = self.context.get('request')
if not request:
return False

View File

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

View File

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

View File

@@ -4,24 +4,6 @@ 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):
@@ -30,7 +12,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(fetch_access_key_secrets),
migrations.AlterField(
model_name="accesskey",
name="secret",
@@ -46,5 +27,4 @@ class Migration(migrations.Migration):
verbose_name="Secret"
),
),
migrations.RunPython(save_access_key_secrets),
]

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ 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',
@@ -14,7 +13,7 @@ __all__ = [
]
class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
class ConnectionTokenSerializer(CommonModelSerializer):
expire_time = serializers.IntegerField(read_only=True, label=_('Expired time'))
input_secret = EncryptedField(
label=_("Input secret"), max_length=40960, required=False, allow_blank=True
@@ -61,7 +60,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin):
validated_data['remote_addr'] = get_request_ip(request)
return super().create(validated_data)
def get_from_ticket_info(self, instance) -> dict:
def get_from_ticket_info(self, instance):
if not instance.from_ticket:
return {}
user = self.get_request_user()

View File

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

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import datetime
import os
from typing import Callable
from urllib.parse import urlparse, urlsplit, urlunsplit, urlencode
from urllib.parse import urlparse
from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY
@@ -155,18 +155,9 @@ 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'),
@@ -174,7 +165,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}'
'cancel_url': reverse('authentication:login') + f'?admin=1&{query_string}'
}
redirect_url = FlashMessageUtil.gen_message_url(message_data)
return redirect_url

View File

@@ -143,9 +143,11 @@ class EncryptMixin:
if value is None:
return value
encryptor = Encryptor(value)
plain_value = encryptor.decrypt()
plain_value = Encryptor(value).decrypt()
# 如果解密失败,则使用原来的值
if not plain_value:
plain_value = value
# 可能和Json mix所以要先解密再json
sp = super()
if hasattr(sp, "from_db_value"):

View File

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

View File

@@ -5,7 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Model
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.fields import empty
from rest_framework.fields import ChoiceField, 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(serializers.ChoiceField):
class LabeledChoiceField(ChoiceField):
def to_representation(self, key):
if key is None:
return key
@@ -180,122 +180,6 @@ 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):
@@ -354,23 +238,6 @@ class BitChoicesField(TreeChoicesField):
value |= name_value_map[name]
return value
def get_schema(self):
"""
为 drf-spectacular 提供 OpenAPI schema
"""
return {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'value': {'type': 'string'},
'label': {'type': 'string'}
}
},
'description': getattr(self, 'help_text', ''),
'title': getattr(self, 'label', ''),
}
def run_validation(self, data=empty):
"""
备注:

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import socket
import time
import uuid
from collections import OrderedDict
from functools import wraps, cached_property
from functools import wraps
from itertools import chain
import html2text
@@ -246,19 +246,17 @@ 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
lazyproperty = cached_property
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_disk_usage(path):

View File

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

View File

@@ -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 + D)",
"StopHotKey": "Stop (Ctrl + C)",
"Submit": "Submit",
"Total": "Total",
"Type": "Type",

View File

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

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130 ops/models/job.py:155
#: authentication/serializers/connect_token_secret.py:128 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
msgid "Delete"
msgstr ""
@@ -3287,7 +3287,7 @@ msgstr ""
msgid "Rename dir"
msgstr ""
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
msgid "Create"
msgstr ""
@@ -4112,7 +4112,7 @@ msgid "Input secret"
msgstr ""
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "Builtin"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr ""
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr ""
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr ""
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr ""
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr ""
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr ""
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr ""
@@ -6136,27 +6136,27 @@ msgstr ""
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "System Admin"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "System Auditor"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "System Component"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "Organization Admin"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "Organization Auditor"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "Organization user"
@@ -6192,30 +6192,6 @@ 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 ""
@@ -6333,28 +6309,24 @@ msgstr ""
msgid "Job audit"
msgstr ""
#: rbac/tree.py:71
msgid "Report"
msgstr ""
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "Organizations"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr ""
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr ""
#: rbac/tree.py:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr ""
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr ""
@@ -6382,22 +6354,6 @@ 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 ""
@@ -7627,11 +7583,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
@@ -9309,7 +9265,7 @@ msgstr ""
msgid "Body"
msgstr ""
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr ""
@@ -9318,7 +9274,7 @@ msgstr ""
msgid "Ticket flow approval rule"
msgstr ""
#: tickets/models/flow.py:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130
#: authentication/serializers/connect_token_secret.py:128
#: 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
msgid "Delete"
msgstr "Eliminar"
@@ -3480,7 +3480,7 @@ msgstr "Descargar"
msgid "Rename dir"
msgstr "Mapear directorio"
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
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:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "Incorporado"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr "Opciones"
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "Componentes"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr "Dominio"
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr "Expiración inmediata"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "Nombre de imagen"
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "Puerto de imagen"
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: 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:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "Administrador del sistema"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "Auditor del sistema"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "Componente del sistema"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "Administrador de la organización"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "Auditor de la organización"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "Usuario de la organización"
@@ -6591,30 +6591,6 @@ 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"
@@ -6734,28 +6710,24 @@ msgstr "Licencia"
msgid "Job audit"
msgstr "Auditoría de acciones"
#: rbac/tree.py:71
msgid "Report"
msgstr "Informe"
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "Gestión de organizaciones"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr "Comentarios de órdenes de trabajo"
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 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:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr "Configuración general"
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr "Ver árbol de autorizaciones"
@@ -6783,26 +6755,6 @@ 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"
@@ -8266,11 +8218,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"
@@ -10092,7 +10044,7 @@ msgstr "Nombre de usuario"
msgid "Body"
msgstr "Contenido"
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "Nivel de aprobación"
@@ -10101,7 +10053,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:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr "Proceso de la orden de trabajo"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130
#: authentication/serializers/connect_token_secret.py:128
#: 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
msgid "Delete"
msgstr "削除"
@@ -3329,7 +3329,7 @@ msgstr "ダウンロード"
msgid "Rename dir"
msgstr "マップディレクトリ"
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
msgid "Create"
msgstr "作成"
@@ -4155,7 +4155,7 @@ msgid "Input secret"
msgstr "カスタムパスワード"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "ビルトイン"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr "オプション"
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "コンポーネント"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr "ドメイン"
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr "すぐに期限切れ"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "ミラー名"
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "ミラーポート"
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "ミラープロトコル"
@@ -6184,27 +6184,27 @@ msgstr "{} 少なくとも1つのシステムロール"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "システム管理者"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "システム監査人"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "システムコンポーネント"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "組織管理者"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "監査員を組織する"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "組織ユーザー"
@@ -6240,30 +6240,6 @@ 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 "コンテンツタイプ"
@@ -6381,28 +6357,24 @@ msgstr "ライセンス"
msgid "Job audit"
msgstr "作業監査"
#: rbac/tree.py:71
msgid "Report"
msgstr "レポート"
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "アプリ組織"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr "チケットコメント"
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "チケット"
#: rbac/tree.py:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr "共通設定"
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr "権限ツリーの表示"
@@ -6430,26 +6402,6 @@ 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がオンになっていない"
@@ -7727,11 +7679,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
@@ -9434,7 +9386,7 @@ msgstr "ユーザー表示名"
msgid "Body"
msgstr "ボディ"
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "レベルを承認する"
@@ -9443,7 +9395,7 @@ msgstr "レベルを承認する"
msgid "Ticket flow approval rule"
msgstr "チケットフロー承認ルール"
#: tickets/models/flow.py:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr "チケットの流れ"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130
#: authentication/serializers/connect_token_secret.py:128
#: 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
msgid "Delete"
msgstr "삭제"
@@ -3320,7 +3320,7 @@ msgstr "다운로드"
msgid "Rename dir"
msgstr "디렉터리 매핑"
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
msgid "Create"
msgstr "생성"
@@ -4154,7 +4154,7 @@ msgid "Input secret"
msgstr "비밀번호 사용자 정의"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "내장된"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr "옵션"
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "구성 요소"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr "도메인"
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr "즉시 만료"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "이미지 이름"
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "이미지 포트"
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "이미지 프로토콜"
@@ -6206,27 +6206,27 @@ msgstr "{} 최소한 하나의 시스템 역할이 필요합니다."
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "시스템 관리자"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "시스템 감사자"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "시스템 구성 요소"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "조직 관리자"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "조직 감사자"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "조직 사용자"
@@ -6276,30 +6276,6 @@ 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 "내용 유형"
@@ -6417,28 +6393,24 @@ msgstr "라이센스"
msgid "Job audit"
msgstr "작업 감사"
#: rbac/tree.py:71
msgid "Report"
msgstr "보고서"
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "조직 관리"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr "작업 댓글"
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "작업"
#: rbac/tree.py:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr "일반 설정"
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr "권한 트리 보기"
@@ -6466,26 +6438,6 @@ 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가 활성화되어 있지 않습니다"
@@ -7789,11 +7741,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
@@ -9499,7 +9451,7 @@ msgstr "사용자 표시 이름"
msgid "Body"
msgstr "내용"
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "승인 수준"
@@ -9508,7 +9460,7 @@ msgstr "승인 수준"
msgid "Ticket flow approval rule"
msgstr "작업 요청 승인 정보"
#: tickets/models/flow.py:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr "작업 요청 프로세스"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130
#: authentication/serializers/connect_token_secret.py:128
#: 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
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:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
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:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "Incorporado"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr "Opções"
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "Componentes"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr "Domínio da Web"
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr "Expiração Imediata"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "Nome da Imagem"
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "Porta da Imagem"
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: 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:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "Administrador do sistema"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "Auditor do sistema"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "Componentes do sistema"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "Administrador da organização"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "Auditor da organização"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "Organizar usuários"
@@ -6484,30 +6484,6 @@ 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"
@@ -6627,28 +6603,24 @@ msgstr "Licença"
msgid "Job audit"
msgstr "Auditoria de Tarefas"
#: rbac/tree.py:71
msgid "Report"
msgstr "Relatório"
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "Gerenciamento de Organização"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr "Comentários de Ordem de Serviço"
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 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:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr "Configurações Gerais"
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr "Visualizar Árvore de Autorização"
@@ -6676,26 +6648,6 @@ 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"
@@ -8079,11 +8031,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
@@ -9869,7 +9821,7 @@ msgstr "Nome de exibição do usuário"
msgid "Body"
msgstr "Conteúdo"
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "Nível de aprovação"
@@ -9878,7 +9830,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:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr "Fluxo de Action da ordem de serviço"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130
#: authentication/serializers/connect_token_secret.py:128
#: 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
msgid "Delete"
msgstr "Удалить"
@@ -3425,7 +3425,7 @@ msgstr "Скачать"
msgid "Rename dir"
msgstr "Сопоставление каталога"
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
msgid "Create"
msgstr "Создать"
@@ -4280,7 +4280,7 @@ msgid "Input secret"
msgstr "Введите пароль"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "Встроенный"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr "Параметры"
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "Компоненты"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr "Зона"
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr "Истекает немедленно"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "Название образа"
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "Порт образа"
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "Протокол образа"
@@ -6402,27 +6402,27 @@ msgstr "{} хотя бы одна системная роль"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "Системный администратор"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "Системный аудитор"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "Системные компоненты"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "Администратор организации"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "Аудитор организации"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "Пользователь организации"
@@ -6458,30 +6458,6 @@ 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 "Тип содержимого"
@@ -6601,28 +6577,24 @@ msgstr "Лицензия"
msgid "Job audit"
msgstr "Аудит заданий"
#: rbac/tree.py:71
msgid "Report"
msgstr "Отчёт"
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "Управление организациями"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr "Комментарии к заявке"
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "Заявка"
#: rbac/tree.py:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr "Общие настройки"
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr "Просмотр дерева разрешений"
@@ -6650,26 +6622,6 @@ 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 не включен"
@@ -8046,11 +7998,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
@@ -9834,7 +9786,7 @@ msgstr "Имя пользователя"
msgid "Body"
msgstr "Содержимое"
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "Уровень одобрения"
@@ -9843,7 +9795,7 @@ msgstr "Уровень одобрения"
msgid "Ticket flow approval rule"
msgstr "Правило утверждения потока заявок"
#: tickets/models/flow.py:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr "Поток заявок"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130 ops/models/job.py:155
#: authentication/serializers/connect_token_secret.py:128 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
msgid "Delete"
msgstr "删除"
@@ -3349,7 +3349,7 @@ msgstr "下载"
msgid "Rename dir"
msgstr "映射目录"
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
msgid "Create"
msgstr "创建"
@@ -4181,7 +4181,7 @@ msgid "Input secret"
msgstr "自定义密码"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "内置的"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr "选项"
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "组件"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr "网域"
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr "立刻过期"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "镜像名称"
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "镜像端口"
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "镜像协议"
@@ -6239,27 +6239,27 @@ msgstr "{} 至少有一个系统角色"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "系统管理员"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "系统审计员"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "系统组件"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "组织管理员"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "组织审计员"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "组织用户"
@@ -6295,30 +6295,6 @@ 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 "内容类型"
@@ -6436,28 +6412,24 @@ msgstr "许可证"
msgid "Job audit"
msgstr "作业审计"
#: rbac/tree.py:71
msgid "Report"
msgstr "报表"
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "组织管理"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr "工单评论"
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "工单"
#: rbac/tree.py:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr "一般设置"
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr "查看授权树"
@@ -6485,26 +6457,6 @@ 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 没有开启"
@@ -7523,9 +7475,7 @@ 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"
@@ -7789,11 +7739,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
@@ -9518,7 +9468,7 @@ msgstr "用户显示名称"
msgid "Body"
msgstr "内容"
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "审批级别"
@@ -9527,7 +9477,7 @@ msgstr "审批级别"
msgid "Ticket flow approval rule"
msgstr "工单批准信息"
#: tickets/models/flow.py:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr "工单流程"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-22 18:01+0800\n"
"POT-Creation-Date: 2025-08-21 11: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:291
#: accounts/const/account.py:33 audits/const.py:24 rbac/tree.py:283
#: 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:117
#: authentication/serializers/connect_token_secret.py:174 labels/models.py:11
#: authentication/serializers/connect_token_secret.py:115
#: authentication/serializers/connect_token_secret.py:172 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:121
#: authentication/serializers/connect_token_secret.py:119
#: 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:130
#: authentication/serializers/connect_token_secret.py:128
#: 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:43 tickets/models/ticket/apply_application.py:16
#: tickets/models/flow.py:42 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:164
#: authentication/serializers/connect_token_secret.py:162
#: 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:133 rbac/models/rolebinding.py:49
#: rbac/builtin.py:127 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:123
#: authentication/serializers/connect_token_secret.py:121
#: 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:122
#: authentication/serializers/connect_token_secret.py:120
#: 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:15
#: assets/models/asset/common.py:184 assets/serializers/asset/custom.py:14
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:129
#: authentication/serializers/connect_token_secret.py:127
#: 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:128
#: authentication/serializers/connect_token_secret.py:126
#: 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:119
#: authentication/serializers/connect_token_secret.py:117
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:292
#: rbac/tree.py:284
msgid "Delete"
msgstr "刪除"
@@ -3308,7 +3308,7 @@ msgstr "下載"
msgid "Rename dir"
msgstr "映射目錄"
#: audits/const.py:23 rbac/tree.py:290 terminal/api/session/session.py:285
#: audits/const.py:23 rbac/tree.py:282 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:289
#: rbac/tree.py:281
msgid "Create"
msgstr "創建"
@@ -4131,7 +4131,7 @@ msgid "Input secret"
msgstr "自訂密碼"
#: authentication/models/connection_token.py:46
#: authentication/serializers/connect_token_secret.py:118
#: authentication/serializers/connect_token_secret.py:116
#: 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:120
#: authentication/serializers/connect_token_secret.py:118
msgid "Is builtin"
msgstr "內建的"
#: authentication/serializers/connect_token_secret.py:124
#: authentication/serializers/connect_token_secret.py:122
msgid "Options"
msgstr "選項"
#: authentication/serializers/connect_token_secret.py:131
#: authentication/serializers/connect_token_secret.py:129
#: ops/notifications.py:19 rbac/tree.py:63
msgid "Component"
msgstr "組件"
#: authentication/serializers/connect_token_secret.py:140
#: authentication/serializers/connect_token_secret.py:138
msgid "Domain"
msgstr "網域"
#: authentication/serializers/connect_token_secret.py:142
#: authentication/serializers/connect_token_secret.py:140
msgid "Expired now"
msgstr "立刻過期"
#: authentication/serializers/connect_token_secret.py:175
#: authentication/serializers/connect_token_secret.py:173
#: terminal/models/virtualapp/virtualapp.py:25
msgid "Image name"
msgstr "鏡像名稱"
#: authentication/serializers/connect_token_secret.py:176
#: authentication/serializers/connect_token_secret.py:174
#: terminal/models/virtualapp/virtualapp.py:27
msgid "Image port"
msgstr "鏡像埠"
#: authentication/serializers/connect_token_secret.py:177
#: authentication/serializers/connect_token_secret.py:175
#: terminal/models/virtualapp/virtualapp.py:26
msgid "Image protocol"
msgstr "鏡像協議"
@@ -6158,27 +6158,27 @@ msgstr "{} 至少有一個系統角色"
msgid "App RBAC"
msgstr "RBAC"
#: rbac/builtin.py:124
#: rbac/builtin.py:118
msgid "SystemAdmin"
msgstr "系統管理員"
#: rbac/builtin.py:127
#: rbac/builtin.py:121
msgid "SystemAuditor"
msgstr "系統審計員"
#: rbac/builtin.py:130
#: rbac/builtin.py:124
msgid "SystemComponent"
msgstr "系統組件"
#: rbac/builtin.py:136
#: rbac/builtin.py:130
msgid "OrgAdmin"
msgstr "組織管理員"
#: rbac/builtin.py:139
#: rbac/builtin.py:133
msgid "OrgAuditor"
msgstr "組織審計員"
#: rbac/builtin.py:142
#: rbac/builtin.py:136
msgid "OrgUser"
msgstr "組織用戶"
@@ -6214,30 +6214,6 @@ 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 "內容類型"
@@ -6355,28 +6331,24 @@ msgstr "許可證"
msgid "Job audit"
msgstr "作業審核"
#: rbac/tree.py:71
msgid "Report"
msgstr "報表"
#: rbac/tree.py:181
#: rbac/tree.py:173
msgid "App organizations"
msgstr "組織管理"
#: rbac/tree.py:182
#: rbac/tree.py:174
msgid "Ticket comment"
msgstr "工單評論"
#: rbac/tree.py:183 settings/serializers/feature.py:174
#: rbac/tree.py:175 settings/serializers/feature.py:174
#: settings/serializers/feature.py:176 tickets/models/ticket/general.py:310
msgid "Ticket"
msgstr "工單管理"
#: rbac/tree.py:184
#: rbac/tree.py:176
msgid "Common setting"
msgstr "一般設定"
#: rbac/tree.py:185
#: rbac/tree.py:177
msgid "View permission tree"
msgstr "查看授權樹"
@@ -6404,26 +6376,6 @@ 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 沒有開啟"
@@ -7676,11 +7628,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
@@ -9360,7 +9312,7 @@ msgstr "用戶顯示名稱"
msgid "Body"
msgstr "內容"
#: tickets/models/flow.py:21 tickets/models/flow.py:48
#: tickets/models/flow.py:21 tickets/models/flow.py:47
#: tickets/models/ticket/general.py:45
msgid "Approve level"
msgstr "審批級別"
@@ -9369,7 +9321,7 @@ msgstr "審批級別"
msgid "Ticket flow approval rule"
msgstr "工單批准資訊"
#: tickets/models/flow.py:53
#: tickets/models/flow.py:52
msgid "Ticket flow"
msgstr "工單流程"

View File

@@ -102,7 +102,7 @@
"Aliyun": "Alibaba cloud",
"All": "All",
"AllAccountTip": "All accounts already added on the asset",
"AllAccounts": "All accounts",
"AllAccounts": "All existing accounts",
"AllClickRead": "Mark all as read",
"AllMembers": "All members",
"AllowInvalidCert": "Ignore certificate check",
@@ -575,7 +575,6 @@
"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",

View File

@@ -574,7 +574,6 @@
"Exclude": "不包含",
"ExcludeAsset": "跳过的资产",
"ExcludeSymbol": "排除字符",
"ExcludeAccount": "排除账号",
"ExecCloudSyncErrorMsg": "云账号配置不完整,请更新后重试",
"Execute": "执行",
"ExecuteAfterSaving": "保存后执行",
@@ -1625,8 +1624,5 @@
"removeWarningMsg": "你确定要移除",
"setVariable": "设置参数",
"userId": "用户ID",
"userName": "用户名",
"ExportAsPDF": "导出 PDF",
"EMailReport": "发送邮件报告",
"Print": "打印"
"userName": "用户名"
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
# views.py
from drf_spectacular.utils import extend_schema
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.routers import DefaultRouter
from rest_framework.views import APIView
@@ -50,34 +51,32 @@ list_schema = {
}
}
from drf_spectacular.openapi import OpenApiResponse, OpenApiExample
list_response = openapi.Response("Resource list response", schema=openapi.Schema(**list_schema))
class ResourceListApi(ProxyMixin, APIView):
@extend_schema(
@swagger_auto_schema(
operation_id="get_resource_list",
summary="Get resource list",
parameters=list_params,
responses={200: OpenApiResponse(description="Resource list response")},
description="""
operation_summary="Get resource list",
manual_parameters=list_params,
responses={200: list_response},
operation_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)
@extend_schema(
@swagger_auto_schema(
operation_id="create_resource_by_type",
summary="Create resource",
parameters=create_params,
description="""
operation_summary="Create resource",
manual_parameters=create_params,
operation_description="""
Create resource,
OPTIONS /api/v1/resources/{resource}/?action=post to get every resource type field type and helptext, and
you will know how to create it.
""",
)
""")
def post(self, request, resource, pk=None):
if not resource:
resource = request.data.pop('resource', '')

View File

@@ -1,6 +1,6 @@
# views.py
from drf_spectacular.utils import extend_schema
from drf_yasg.utils import swagger_auto_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]
@extend_schema(
@swagger_auto_schema(
operation_id="get_supported_resources",
summary="Get-all-support-resources",
description="Get all support resources, name, path, verbose_name description",
operation_summary="Get-all-support-resources",
operation_description="Get all support resources, name, path, verbose_name description",
responses={200: ResourceTypeResourceSerializer(many=True)}, # Specify the response serializer
)
def get(self, request):

View File

@@ -6,7 +6,7 @@ from typing import Dict
from django.urls import URLPattern
from django.urls import URLResolver
from drf_spectacular.utils import OpenApiParameter
from drf_yasg import openapi
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 OpenApiParameter(
name=d['name'], location=d['in'],
return openapi.Parameter(
d['name'], d['in'],
description=d['description'], type=d['type'], required=d.get('required', False)
)

View File

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

View File

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

View File

@@ -15,15 +15,6 @@ 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', None) != 'list' and not getattr(view, 'default_limit', None):
self.default_limit = None
if view and hasattr(view, 'page_default_limit'):
self.default_limit = view.page_default_limit
if view and hasattr(view, 'default_limit'):
self.default_limit = view.default_limit
return super().paginate_queryset(queryset, request, view)

View File

@@ -138,8 +138,7 @@ INSTALLED_APPS = [
'labels.apps.LabelsConfig',
'reports.apps.ReportsConfig',
'rest_framework',
'drf_spectacular',
'drf_spectacular_sidecar',
'drf_yasg',
'django_cas_ng',
'channels',
'django_filters',

View File

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

View File

@@ -49,44 +49,21 @@ 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',
}
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',
SWAGGER_SETTINGS = {
'DEFAULT_AUTO_SCHEMA_CLASS': 'jumpserver.views.swagger.CustomSwaggerAutoSchema',
'USE_SESSION_AUTH': True,
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
},
'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': []}],
'DEFAULT_INFO': 'jumpserver.views.swagger.api_info',
}
# Captcha settings, more see https://django-simple-captcha.readthedocs.io/en/latest/advanced.html
CAPTCHA_IMAGE_SIZE = (180, 38)
CAPTCHA_FOREGROUND_COLOR = '#001100'

View File

@@ -38,7 +38,6 @@ 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 = [
@@ -95,10 +94,9 @@ cache_kwargs = {
}
# docs 路由
urlpatterns += [
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'),
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'),
]
if os.environ.get('DEBUG_TOOLBAR', False):

View File

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

View File

@@ -1,57 +1,176 @@
import os
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
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 SwaggerUI(LoginRequiredMixin, SpectacularSwaggerView):
pass
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)
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"},
@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'
]
if request.scheme == 'http':
schema['servers'] = schema['servers'][::-1]
for p in excludes:
if path.find(p) >= 0:
return True
return False
schema['components']['securitySchemes'] = {
'Bearer': {
'type': 'http',
'scheme': 'bearer',
'bearerFormat': 'JWT',
}
}
return Response(schema)
def exclude_some_app_model(self, path):
parts = path.split('/')
if len(parts) < 5:
return False
@method_decorator(cache_page(60 * 5,), name="dispatch")
class JsonApi(SchemeMixin, SpectacularJSONAPIView):
pass
apps = []
if self.from_mcp:
apps = [
'ops', 'tickets', 'authentication',
'settings', 'xpack', 'terminal', 'rbac',
'notifications', 'promethues', 'acls'
]
@method_decorator(cache_page(60 * 5,), name="dispatch")
class YamlApi(SchemeMixin, SpectacularYAMLAPIView):
pass
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
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()
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"),
)
def get_swagger_view():
from ..urls import api_v1
from django.urls import path, include
patterns = [
path('api/v1/', include(api_v1))
]
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

View File

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

View File

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

View File

@@ -1,401 +0,0 @@
#!/usr/bin/python
# Modified from ansible_collections.community.general.mssql_script
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r"""
module: mssql_script
short_description: Execute SQL scripts on a MSSQL database
version_added: "1.0.0"
description:
- Execute SQL scripts on a MSSQL database.
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: partial
details:
- The script is not be executed in check mode.
diff_mode:
support: none
options:
name:
description: Database to run script against.
aliases: [db]
default: ''
type: str
login_user:
description: The username used to authenticate with.
type: str
login_password:
description: The password used to authenticate with.
type: str
login_host:
description: Host running the database.
type: str
required: true
login_port:
description: Port of the MSSQL server. Requires O(login_host) be defined as well.
default: 1433
type: int
script:
description:
- The SQL script to be executed.
- Script can contain multiple SQL statements. Multiple Batches can be separated by V(GO) command.
- Each batch must return at least one result set.
required: true
type: str
transaction:
description:
- If transactional mode is requested, start a transaction and commit the change only if the script succeed. Otherwise,
rollback the transaction.
- If transactional mode is not requested (default), automatically commit the change.
type: bool
default: false
version_added: 8.4.0
output:
description:
- With V(default) each row is returned as a list of values. See RV(query_results).
- Output format V(dict) returns dictionary with the column names as keys. See RV(query_results_dict).
- V(dict) requires named columns to be returned by each query otherwise an error is thrown.
choices: ["dict", "default"]
default: 'default'
type: str
params:
description: |-
Parameters passed to the script as SQL parameters.
(Query V('SELECT %(name\)s"') with V(example: '{"name": "John Doe"}).)'.
type: dict
notes:
- Requires the pymssql Python package on the remote host. For Ubuntu, this is as easy as C(pip install pymssql) (See M(ansible.builtin.pip)).
requirements:
- pymssql
author:
- Kris Budde (@kbudde)
"""
EXAMPLES = r"""
- name: Check DB connection
community.general.mssql_script:
login_user: "{{ mssql_login_user }}"
login_password: "{{ mssql_login_password }}"
login_host: "{{ mssql_host }}"
login_port: "{{ mssql_port }}"
db: master
script: "SELECT 1"
- name: Query with parameter
community.general.mssql_script:
login_user: "{{ mssql_login_user }}"
login_password: "{{ mssql_login_password }}"
login_host: "{{ mssql_host }}"
login_port: "{{ mssql_port }}"
script: |
SELECT name, state_desc FROM sys.databases WHERE name = %(dbname)s
params:
dbname: msdb
register: result_params
- assert:
that:
- result_params.query_results[0][0][0][0] == 'msdb'
- result_params.query_results[0][0][0][1] == 'ONLINE'
- name: Query within a transaction
community.general.mssql_script:
login_user: "{{ mssql_login_user }}"
login_password: "{{ mssql_login_password }}"
login_host: "{{ mssql_host }}"
login_port: "{{ mssql_port }}"
script: |
UPDATE sys.SomeTable SET desc = 'some_table_desc' WHERE name = %(dbname)s
UPDATE sys.AnotherTable SET desc = 'another_table_desc' WHERE name = %(dbname)s
transaction: true
params:
dbname: msdb
- name: two batches with default output
community.general.mssql_script:
login_user: "{{ mssql_login_user }}"
login_password: "{{ mssql_login_password }}"
login_host: "{{ mssql_host }}"
login_port: "{{ mssql_port }}"
script: |
SELECT 'Batch 0 - Select 0'
SELECT 'Batch 0 - Select 1'
GO
SELECT 'Batch 1 - Select 0'
register: result_batches
- assert:
that:
- result_batches.query_results | length == 2 # two batch results
- result_batches.query_results[0] | length == 2 # two selects in first batch
- result_batches.query_results[0][0] | length == 1 # one row in first select
- result_batches.query_results[0][0][0] | length == 1 # one column in first row
- result_batches.query_results[0][0][0][0] == 'Batch 0 - Select 0' # each row contains a list of values.
- name: two batches with dict output
community.general.mssql_script:
login_user: "{{ mssql_login_user }}"
login_password: "{{ mssql_login_password }}"
login_host: "{{ mssql_host }}"
login_port: "{{ mssql_port }}"
output: dict
script: |
SELECT 'Batch 0 - Select 0' as b0s0
SELECT 'Batch 0 - Select 1' as b0s1
GO
SELECT 'Batch 1 - Select 0' as b1s0
register: result_batches_dict
- assert:
that:
- result_batches_dict.query_results_dict | length == 2 # two batch results
- result_batches_dict.query_results_dict[0] | length == 2 # two selects in first batch
- result_batches_dict.query_results_dict[0][0] | length == 1 # one row in first select
- result_batches_dict.query_results_dict[0][0][0]['b0s0'] == 'Batch 0 - Select 0' # column 'b0s0' of first row
"""
RETURN = r"""
query_results:
description: List of batches (queries separated by V(GO) keyword).
type: list
elements: list
returned: success and O(output=default)
sample:
[
[
[
[
"Batch 0 - Select 0"
]
],
[
[
"Batch 0 - Select 1"
]
]
],
[
[
[
"Batch 1 - Select 0"
]
]
]
]
contains:
queries:
description:
- List of result sets of each query.
- If a query returns no results, the results of this and all the following queries are not included in the output.
- Use the V(GO) keyword in O(script) to separate queries.
type: list
elements: list
contains:
rows:
description: List of rows returned by query.
type: list
elements: list
contains:
column_value:
description:
- List of column values.
- Any non-standard JSON type is converted to string.
type: list
example: ["Batch 0 - Select 0"]
returned: success, if output is default
query_results_dict:
description: List of batches (queries separated by V(GO) keyword).
type: list
elements: list
returned: success and O(output=dict)
sample:
[
[
[
[
"Batch 0 - Select 0"
]
],
[
[
"Batch 0 - Select 1"
]
]
],
[
[
[
"Batch 1 - Select 0"
]
]
]
]
contains:
queries:
description:
- List of result sets of each query.
- If a query returns no results, the results of this and all the following queries are not included in the output.
Use V(GO) keyword to separate queries.
type: list
elements: list
contains:
rows:
description: List of rows returned by query.
type: list
elements: list
contains:
column_dict:
description:
- Dictionary of column names and values.
- Any non-standard JSON type is converted to string.
type: dict
example: {"col_name": "Batch 0 - Select 0"}
returned: success, if output is dict
"""
import pymssql
from ansible.module_utils.basic import AnsibleModule
import json
def clean_output(o):
return str(o)
def main():
module_args = dict(
name=dict(aliases=['db'], default=''),
login_user=dict(type='str', required=False, default=None),
login_password=dict(no_log=True),
login_host=dict(required=True),
login_port=dict(type='int', default=1433),
script=dict(required=True),
output=dict(default='default', choices=['dict', 'default']),
params=dict(type='dict'),
transaction=dict(type='bool', default=True),
tds_version=dict(type='str', required=False, default=None),
encryption=dict(type='str', required=False, default=None)
)
result = dict(
changed=False,
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
db = module.params['name']
login_user = module.params['login_user']
login_password = module.params['login_password']
login_host = module.params['login_host']
login_port = module.params['login_port']
script = module.params['script']
output = module.params['output']
sql_params = module.params['params']
transaction = module.params['transaction']
# TODO 待 ansible 官方支持这两个参数
tds_version = module.params['tds_version'] or None
encryption = module.params['encryption'] or None
login_querystring = login_host
if login_port != 1433:
login_querystring = "%s:%s" % (login_host, login_port)
if login_user is not None and login_password is None:
module.fail_json(
msg="when supplying login_user argument, login_password must also be provided")
try:
conn = pymssql.connect(
user=login_user, password=login_password, host=login_querystring,
database=db, encryption=encryption, tds_version=tds_version)
cursor = conn.cursor()
except Exception as e:
if "Unknown database" in str(e):
errno, errstr = e.args
module.fail_json(msg="ERROR: %s %s" % (errno, errstr))
else:
module.fail_json(
msg="unable to connect, check login_user and login_password are correct, or alternatively check your "
"@sysconfdir@/freetds.conf / ${HOME}/.freetds.conf")
# If transactional mode is requested, start a transaction
conn.autocommit(not transaction)
query_results_key = 'query_results'
if output == 'dict':
cursor = conn.cursor(as_dict=True)
query_results_key = 'query_results_dict'
# Process the script into batches
queries = []
current_batch = []
for statement in script.splitlines(True):
# Ignore the Byte Order Mark, if found
if statement.strip() == '\uFEFF':
continue
# Assume each 'GO' is on its own line but may have leading/trailing whitespace
# and be of mixed-case
if statement.strip().upper() != 'GO':
current_batch.append(statement)
else:
queries.append(''.join(current_batch))
current_batch = []
if len(current_batch) > 0:
queries.append(''.join(current_batch))
result['changed'] = True
if module.check_mode:
module.exit_json(**result)
query_results = []
for query in queries:
# Catch and exit on any bad query errors
try:
cursor.execute(query, sql_params)
qry_result = []
rows = cursor.fetchall()
while rows:
qry_result.append(rows)
rows = cursor.fetchall()
query_results.append(qry_result)
except Exception as e:
# We know we executed the statement so this error just means we have no resultset
# which is ok (eg UPDATE/INSERT)
if (
type(e).__name__ == 'OperationalError' and
str(e) == 'Statement not executed or executed statement has no resultset'
):
query_results.append([])
else:
# Rollback transaction before failing the module in case of error
if transaction:
conn.rollback()
error_msg = '%s: %s' % (type(e).__name__, str(e))
module.fail_json(msg="query failed", query=query, error=error_msg, **result)
# Commit transaction before exiting the module in case of no error
if transaction:
conn.commit()
# ensure that the result is json serializable
qry_results = json.loads(json.dumps(query_results, default=clean_output))
result[query_results_key] = qry_results
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

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

View File

@@ -6,7 +6,6 @@ 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

View File

@@ -8,7 +8,7 @@ from ..models import MessageContent
class SenderMixin(ModelSerializer):
sender = serializers.SerializerMethodField()
def get_sender(self, site_msg) -> str:
def get_sender(self, site_msg):
sender = site_msg.sender
if sender:
return str(sender)
@@ -28,15 +28,13 @@ class MessageContentSerializer(SenderMixin, ModelSerializer):
]
@staticmethod
def get_message(site_msg) -> str:
def get_message(site_msg):
markdown = convert_html_to_markdown(site_msg.message)
return markdown
class SiteMessageSerializer(SenderMixin, ModelSerializer):
content = MessageContentSerializer(read_only=True)
has_read = serializers.BooleanField(read_only=True)
read_at = serializers.DateTimeField(read_only=True)
class Meta:
model = MessageContent

View File

@@ -112,21 +112,12 @@ class JMSInventory:
@staticmethod
def make_protocol_setting_vars(host, protocols):
# 针对 ssh sqlserver 协议的特殊处理
# 针对 ssh 协议的特殊处理
for p in protocols:
if p.name == 'ssh':
if hasattr(p, 'setting'):
setting = getattr(p, 'setting')
host['jms_asset']['old_ssh_version'] = setting.get('old_ssh_version', False)
if p.name == 'sqlserver':
if hasattr(p, 'setting'):
setting = getattr(p, 'setting')
encryption = setting.get('encrypt', True)
version = setting.get('version', ">=2014")
if version == '<2014':
host['jms_asset']['tds_version'] = '7.0'
if not encryption:
host['jms_asset']['encryption'] = 'off'
host['old_ssh_version'] = setting.get('old_ssh_version', False)
def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway, path_dir,
ansible_config):
@@ -235,6 +226,7 @@ class JMSInventory:
})
self.make_protocol_setting_vars(host, protocols)
protocols = host['jms_asset']['protocols']
host['jms_asset'].update({f"{p['name']}_port": p['port'] for p in protocols})
if host['jms_account'] and tp == 'oracle':

View File

@@ -19,7 +19,7 @@ class CeleryTask(models.Model):
__state = None
@property
def meta(self) -> dict:
def meta(self):
task = app.tasks.get(self.name, None)
return {
"comment": getattr(task, 'verbose_name', None),
@@ -28,7 +28,7 @@ class CeleryTask(models.Model):
}
@property
def summary(self) -> dict:
def summary(self):
if self.__summary is not None:
return self.__summary
executions = CeleryTaskExecution.objects.filter(name=self.name)
@@ -53,7 +53,7 @@ class CeleryTask(models.Model):
return color
@property
def state(self) -> str:
def state(self):
if self.__state is not None:
return self.__state
last_five_executions = CeleryTaskExecution.objects.filter(

View File

@@ -1,11 +1,11 @@
import json
import logging
import os
import sys
import uuid
from collections import defaultdict
from datetime import timedelta, datetime
from datetime import timedelta
import sys
from celery import current_task
from django.conf import settings
from django.db import models
@@ -177,11 +177,11 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
return self.executions.first()
@property
def date_last_run(self) -> datetime:
def date_last_run(self):
return self.last_execution.date_created if self.last_execution else None
@property
def summary(self) -> dict:
def summary(self):
summary = {
"total": self.executions.count(),
"success": self.executions.filter(status=JobStatus.success).count(),
@@ -189,7 +189,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
return summary
@property
def average_time_cost(self) -> int:
def average_time_cost(self):
return self.last_execution.time_cost if self.last_execution else 0
def get_register_task(self):
@@ -207,7 +207,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
user=self.creator, module=self.module)
@property
def material(self) -> str:
def material(self):
if self.type == 'adhoc':
return "{}:{}".format(self.module, self.args)
if self.type == 'playbook':
@@ -271,7 +271,7 @@ class JobExecution(JMSOrgBaseModel):
db_module_name_map = {
'mysql': 'community.mysql.mysql_query',
'postgresql': 'community.postgresql.postgresql_query',
'sqlserver': 'mssql_script',
'sqlserver': 'community.general.mssql_script',
}
extra_query_token_map = {
'sqlserver': 'script'
@@ -292,10 +292,7 @@ class JobExecution(JMSOrgBaseModel):
"login_user={{login_user}} " \
"login_password={{login_password}} " \
"login_port={{login_port}} " \
"%s={{login_db}} " % login_db_token
if module == 'mssql_script':
login_args += "encryption={{jms_asset.encryption | default(None) }} " \
"tds_version={{jms_asset.tds_version | default(None) }} "
"%s={{login_db}}" % login_db_token
shell = "{} {}=\"{}\" ".format(login_args, query_token, self.current_job.args)
return module, shell
@@ -377,7 +374,7 @@ class JobExecution(JMSOrgBaseModel):
return str(self.id).split('-')[-1]
@property
def time_cost(self) -> int:
def time_cost(self):
if not self.date_start:
return 0
if self.is_finished:
@@ -385,25 +382,25 @@ class JobExecution(JMSOrgBaseModel):
return (timezone.now() - self.date_start).total_seconds()
@property
def timedelta(self) -> timedelta:
def timedelta(self):
if self.date_start and self.date_finished:
return self.date_finished - self.date_start
return None
@property
def is_finished(self) -> bool:
def is_finished(self):
return self.status in [JobStatus.success, JobStatus.failed, JobStatus.timeout]
@property
def is_success(self) -> bool:
def is_success(self):
return self.status == JobStatus.success
@property
def inventory_path(self) -> str:
def inventory_path(self):
return os.path.join(self.private_dir, 'inventory', 'hosts')
@property
def private_dir(self) -> str:
def private_dir(self):
uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id
job_name = self.current_job.name if self.current_job.name else 'instant'
return os.path.join(settings.ANSIBLE_DIR, job_name, uniq)
@@ -453,8 +450,6 @@ class JobExecution(JMSOrgBaseModel):
"input": self.material,
"risk_level": RiskLevelChoices.reject,
"user": self.creator,
'org_id': self.org_id,
'_org_name': self.org_name,
}).publish_async()
raise Exception("command is rejected by ACL")
elif acl.is_action(CommandFilterACL.ActionChoices.warning):
@@ -493,8 +488,6 @@ class JobExecution(JMSOrgBaseModel):
"input": self.material,
"risk_level": RiskLevelChoices.reject,
"user": self.creator,
'org_id': self.org_id,
'_org_name': self.org_name,
}).publish_async()
raise CommandInBlackListException(
"Command is rejected by black list: {}".format(self.current_job.args))

View File

@@ -32,7 +32,7 @@ class Variable(JMSBaseModel):
return self.name
@property
def form_data(self) -> dict:
def form_data(self):
return {
'var_name': self.var_name,
'label': self.name,

View File

@@ -42,8 +42,8 @@ class VariableSerializer(CommonBulkModelSerializer):
model = Variable
read_only_fields = ["id", "date_created", "date_updated", "created_by", "creator"]
fields = read_only_fields + [
"name", "var_name", "type", 'required', 'default_value', 'tips',
'adhoc', 'playbook', 'job', 'form_data', 'extra_args'
"name", "var_name", "type", 'required', 'default_value', 'tips', 'adhoc', 'playbook', 'job', 'form_data',
'extra_args'
]
def validate(self, attrs):

View File

@@ -4,8 +4,7 @@
from rest_framework.serializers import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from common.db.models import JMSBaseModel
from common.utils import get_logger, lazyproperty
from ..models import Organization
@@ -81,7 +80,7 @@ class OrgModelMixin(models.Model):
return Organization.get_instance(self.org_id)
@property
def org_name(self) -> str:
def org_name(self):
return self.org.name
@property

View File

@@ -168,17 +168,17 @@ class Organization(OrgRoleMixin, JMSBaseModel):
name = settings.GLOBAL_ORG_DISPLAY_NAME or cls.ROOT_NAME
return cls(id=cls.ROOT_ID, name=name, builtin=True)
def is_root(self) -> bool:
def is_root(self):
return self.id == self.ROOT_ID
def is_default(self) -> bool:
def is_default(self):
return str(self.id) == self.DEFAULT_ID
def is_system(self) -> bool:
def is_system(self):
return str(self.id) == self.SYSTEM_ID
@property
def internal(self) -> bool:
def internal(self):
return str(self.id) in self.INTERNAL_IDS
def change_to(self):

View File

@@ -6,7 +6,6 @@ from django.db.models import F
from django.shortcuts import get_object_or_404
from rest_framework import generics
from users.models import User
from accounts.serializers import AccountSerializer
from orgs.mixins.api import OrgRelationMixin, OrgBulkModelViewSet
from orgs.utils import current_org
@@ -53,7 +52,6 @@ class AssetPermissionAllUserListApi(generics.ListAPIView):
serializer_class = serializers.AssetPermissionAllUserSerializer
filterset_fields = ("username", "name")
search_fields = filterset_fields
queryset = User.objects.none()
def get_queryset(self):
pk = self.kwargs.get("pk")

View File

@@ -26,19 +26,16 @@ class CurrentAssetPermission(object):
class RelationMixin(BulkSerializerMixin, serializers.Serializer):
assetpermission_display = serializers.SerializerMethodField()
assetpermission_display = serializers.ReadOnlyField()
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['assetpermission', "assetpermission_display"])
return fields
def get_assetpermission_display(self, obj) -> str:
return str(obj.assetpermission)
class AssetPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
user_display = serializers.SerializerMethodField()
user_display = serializers.ReadOnlyField()
class Meta:
model = AssetPermission.users.through
@@ -46,9 +43,6 @@ class AssetPermissionUserRelationSerializer(RelationMixin, serializers.ModelSeri
'id', 'user', 'user_display',
]
def get_user_display(self, obj) -> str:
return str(obj.user)
class AssetPermissionAllUserSerializer(serializers.Serializer):
user = serializers.UUIDField(read_only=True, source='id')
@@ -58,12 +52,12 @@ class AssetPermissionAllUserSerializer(serializers.Serializer):
only_fields = ['id', 'username', 'name']
@staticmethod
def get_user_display(obj) -> str:
def get_user_display(obj):
return str(obj)
class AssetPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
usergroup_display = serializers.SerializerMethodField()
usergroup_display = serializers.ReadOnlyField()
class Meta:
model = AssetPermission.user_groups.through
@@ -71,12 +65,9 @@ class AssetPermissionUserGroupRelationSerializer(RelationMixin, serializers.Mode
'id', 'usergroup', "usergroup_display",
]
def get_usergroup_display(self, obj) -> str:
return str(obj.usergroup)
class AssetPermissionAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
asset_display = serializers.SerializerMethodField()
asset_display = serializers.ReadOnlyField()
class Meta:
model = AssetPermission.assets.through
@@ -84,9 +75,6 @@ class AssetPermissionAssetRelationSerializer(RelationMixin, serializers.ModelSer
'id', "asset", "asset_display",
]
def get_asset_display(self, obj) -> str:
return str(obj.asset)
class AssetPermissionAllAssetSerializer(serializers.Serializer):
asset = serializers.UUIDField(read_only=True, source='id')
@@ -96,7 +84,7 @@ class AssetPermissionAllAssetSerializer(serializers.Serializer):
only_fields = ['id', 'name', 'address']
@staticmethod
def get_asset_display(obj) -> str:
def get_asset_display(obj):
return str(obj)
@@ -108,6 +96,3 @@ class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSeri
fields = [
'id', 'node', "node_display",
]
def get_node_display(self, obj) -> str:
return str(obj.node)

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