Compare commits

..

43 Commits
v3.1 ... v3.0

Author SHA1 Message Date
Bai
98a5e97518 fix: 修复日志记录到syslog时中文编码问题 2023-03-15 19:45:16 +08:00
ibuler
17be132497 fix: windows 平台默认不开启 console 2023-03-15 19:00:52 +08:00
feng
0cbce5f07a fix: 账号模版创建带密码的密钥之后无法添加到主机 2023-03-14 11:24:44 +08:00
ibuler
26dfb729bc perf: 优化资产迁移,避免冲突 2023-03-14 11:20:29 +08:00
fit2bot
bbe03ddb73 perf: 修改 ssh key with pass 报错 (#9918)
Co-authored-by: ibuler <ibuler@qq.com>
2023-03-10 16:05:11 +08:00
Bai
c88645fa57 feat: 支持飞书国际版(lark) 2023-03-10 15:48:08 +08:00
Bai
7271feb598 fix: ignore 2023-03-09 18:12:22 +08:00
jiangweidong
950183ca85 fix: 操作日志可能保存明文密码 2023-03-09 12:38:40 +08:00
Eric
6b1aa752d6 fix: 修复存储故障造成的录像获取失败问题 2023-03-09 11:51:37 +08:00
Bai
c908b47ccf fix: 修复组织管理员查看活动日志详情时没有对象的问题 2023-03-08 15:58:05 +08:00
Bai
88af33b7c2 fix: 修复组织管理员查看操作日志可以看到 system 组织下的操作问题, 只有系统管理员可以查看任务监控 2023-03-08 15:22:27 +08:00
老广
136537fcfb Merge pull request #9872 from jumpserver/pr@v3.0@fix_su_from_accounts
fix: 修复su-from-accounts API 500问题,Unsubscribe msg error 转成debug
2023-03-08 14:06:36 +08:00
老广
dfd404c549 Merge pull request #9875 from jumpserver/pr@v3.0@fix_operatelog_hide_sth
fix: 操作日志显示用户加密后的密文,及日期格式调整
2023-03-08 14:05:47 +08:00
老广
53886a5abf Merge pull request #9882 from jumpserver/pr@v3.0@perf_email_test_error
perf: 邮箱测试时,不填写'主题前缀'会报错
2023-03-08 14:02:58 +08:00
jiangweidong
50d3125fec perf: 邮箱测试时,不填写'主题前缀'会报错 2023-03-08 13:51:40 +08:00
jiangweidong
1145305f11 fix: 操作日志显示用户加密后的密文,及日期格式调整 2023-03-08 11:24:56 +08:00
feng
433f063142 fix: 修复su-from-accounts API 500问题,Unsubscribe msg error 转成debug 2023-03-08 11:02:05 +08:00
fit2bot
8c4e496391 fix: 修复i8n 500 (#9848)
Co-authored-by: feng <1304903146@qq.com>
2023-03-02 16:23:02 +08:00
Bai
fad214ccbb fix: 修复 ldap 用户登录时邮箱存在 500 的问题 2023-03-02 16:22:24 +08:00
Bai
3b0f68c4c7 fix: 修复 ldap 用户登录时邮箱存在 500 的问题 2023-03-02 16:11:04 +08:00
Aaron3S
87b4ecfb3a fix: 修复作业执行没有日志权限的问题 2023-03-01 18:34:55 +08:00
Bai
32f2be2793 fix: 资产类型树返回类型节点时, 没有platfrom设置isParent为False, 解决展开节点重复的问题 2023-03-01 17:28:59 +08:00
fit2bot
a098bc6c06 perf: 推送账号 社区版定时任务关闭 (#9805)
Co-authored-by: feng <1304903146@qq.com>
2023-02-28 13:43:38 +08:00
老广
86870ad985 Merge pull request #9798 from jumpserver/pr@v3.0@fix_protocol_init_error
perf: 修改协议创建时一些默认值
2023-02-28 09:45:15 +08:00
ibuler
c602bf7224 perf: 修改协议创建时一些默认值 2023-02-27 11:49:04 +00:00
fit2bot
86dab4fc6e perf: 今日活跃资产 (#9797)
Co-authored-by: feng <1304903146@qq.com>
2023-02-27 18:10:11 +08:00
Aaron3S
a85a80a945 fix: 默认增加普通用户作业中心权限 2023-02-27 17:28:04 +08:00
老广
349edc10aa Merge pull request #9791 from jumpserver/pr@v3.0@add_accounts_suggestions
perf: 添加账号用户名的推荐
2023-02-27 15:19:26 +08:00
ibuler
44918e3cb5 perf: 添加账号用户名的推荐
perf: 修改账号推荐
2023-02-27 07:14:55 +00:00
ibuler
9a2f6c0d70 perf: 修改资产 address 长度,以支持 mb4
perf: 修改长度
2023-02-27 14:08:15 +08:00
ibuler
934969a8f1 perf: 去掉没有 Name 的迁移 2023-02-27 14:02:09 +08:00
老广
57162c1628 Merge pull request #9776 from jumpserver/pr@v3.0@perf_account_migrate2
perf: 优化迁移 accounts
2023-02-27 10:22:59 +08:00
ibuler
32fb36867f perf: 优化迁移 accounts
perf: 优化账号迁移,同名的迁移到历史中
2023-02-26 01:49:25 +00:00
老广
158b589028 Merge pull request #9761 from jumpserver/pr@v3@fix_activity_save_error
fix: 解决Activity保存因为参数出错问题
2023-02-24 18:18:03 +08:00
jiangweidong
d64277353c Merge branch 'v3.0' of http://github.com/jumpserver/jumpserver into pr@v3@fix_activity_save_error 2023-02-24 18:10:47 +08:00
jiangweidong
bff6f397ce fix: 解决Activity保存因为参数出错问题 2023-02-24 18:10:42 +08:00
fit2bot
0ad461a804 perf: 修改host info 接口, 社区开放applet, 修改改密发邮件bug (#9760)
Co-authored-by: feng <1304903146@qq.com>
2023-02-24 18:08:40 +08:00
Bai
a1dcef0ba0 fix: 修复 web gui 支持的数据库 2023-02-24 15:12:08 +08:00
Bai
dbb1ee3a75 fix: 修复认证MFA失败次数清空问题 2023-02-24 14:43:51 +08:00
fit2bot
d6bd207a17 fix: 修复计算今日活跃资产过滤逻辑 (#9744)
Co-authored-by: Bai <baijiangjie@gmail.com>
2023-02-24 12:17:10 +08:00
Bai
e69ba27ff4 fix: 修复获取授权资产详情时返回 spec_info 字段, 解决连接 Magnus 问题 2023-02-24 11:41:47 +08:00
ibuler
adbe7c07c6 perf: 修复社区版可能引起的问题 2023-02-24 00:31:10 +08:00
老广
d1eacf53d4 Merge pull request #9736 from jumpserver/dev
fix: 修复 loong64 grpc 构建失败
2023-02-23 21:50:11 +08:00
67 changed files with 681 additions and 1220 deletions

View File

@@ -7,7 +7,7 @@ assignees: wojiushixiaobai
---
**JumpServer 版本( v2.28 之前的版本不再支持 )**
**JumpServer 版本(v1.5.9以下不再支持)**
**浏览器版本**
@@ -17,6 +17,6 @@ assignees: wojiushixiaobai
**Bug 重现步骤(有截图更好)**
1.
2.
3.
1.
2.
3.

View File

@@ -24,7 +24,6 @@ jobs:
build-args: |
APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple
PIP_JMS_MIRROR=https://pypi.org/simple
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -20,4 +20,4 @@ jobs:
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
with:
source-repo: 'git@github.com:jumpserver/jumpserver.git'
destination-repo: 'git@gitee.com:fit2cloud-feizhiyun/JumpServer.git'
destination-repo: 'git@gitee.com:jumpserver/jumpserver.git'

View File

@@ -10,17 +10,6 @@
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
</p>
<p align="center">
JumpServer <a href="https://github.com/jumpserver/jumpserver/releases/tag/v3.0.0">v3.0</a> 正式发布。
<br>
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
| :warning: 注意 :warning: |
|:-------------------------------------------------------------------------------------------------------------------------:|
| 3.0 架构上和 2.0 变化较大,建议全新安装一套环境来体验。如需升级,请务必升级前进行备份,并[查阅文档](https://kb.fit2cloud.com/?p=06638d69-f109-4333-b5bf-65b17b297ed9) |
--------------------------
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
@@ -38,7 +27,7 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
## UI 展示
![UI展示](https://docs.jumpserver.org/zh/v3/img/dashboard.png)
![UI展示](https://www.jumpserver.org/images/screenshot/1.png)
## 在线体验
@@ -52,7 +41,8 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
## 快速开始
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/)
- [手动安装](https://github.com/jumpserver/installer)
- [产品文档](https://docs.jumpserver.org)
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)

View File

@@ -1,39 +1,15 @@
from django_filters import rest_framework as drf_filters
from assets.const import Protocol
from accounts import serializers
from accounts.models import AccountTemplate
from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission
from common.permissions import UserConfirmation, ConfirmType
from common.views.mixins import RecordViewLogMixin
from common.drf.filters import BaseFilterSet
class AccountTemplateFilterSet(BaseFilterSet):
protocols = drf_filters.CharFilter(method='filter_protocols')
class Meta:
model = AccountTemplate
fields = ('username', 'name')
@staticmethod
def filter_protocols(queryset, name, value):
secret_types = set()
protocols = value.split(',')
protocol_secret_type_map = Protocol.settings()
for p in protocols:
if p not in protocol_secret_type_map:
continue
_st = protocol_secret_type_map[p].get('secret_types', [])
secret_types.update(_st)
queryset = queryset.filter(secret_type__in=secret_types)
return queryset
from orgs.mixins.api import OrgBulkModelViewSet
from accounts import serializers
from accounts.models import AccountTemplate
class AccountTemplateViewSet(OrgBulkModelViewSet):
model = AccountTemplate
filterset_class = AccountTemplateFilterSet
filterset_fields = ("username", 'name')
search_fields = ('username', 'name')
serializer_classes = {
'default': serializers.AccountTemplateSerializer

View File

@@ -9,12 +9,12 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
when: account.secret_type == "password"
when: secret_type == "password"
- name: create user If it already exists, no operation will be performed
ansible.builtin.user:
name: "{{ account.username }}"
when: account.secret_type == "ssh_key"
when: secret_type == "ssh_key"
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
@@ -22,7 +22,7 @@
regexp: "{{ kwargs.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- secret_type == "ssh_key"
- kwargs.strategy == "set_jms"
- name: Change SSH key
@@ -30,7 +30,7 @@
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ kwargs.exclusive }}"
when: account.secret_type == "ssh_key"
when: secret_type == "ssh_key"
- name: Refresh connection
ansible.builtin.meta: reset_connection
@@ -42,7 +42,7 @@
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
when: account.secret_type == "password"
when: secret_type == "password"
- name: Verify SSH key
ansible.builtin.ping:
@@ -51,4 +51,4 @@
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
when: account.secret_type == "ssh_key"
when: secret_type == "ssh_key"

View File

@@ -9,12 +9,12 @@
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
update_password: always
when: account.secret_type == "password"
when: secret_type == "password"
- name: create user If it already exists, no operation will be performed
ansible.builtin.user:
name: "{{ account.username }}"
when: account.secret_type == "ssh_key"
when: secret_type == "ssh_key"
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
@@ -22,7 +22,7 @@
regexp: "{{ kwargs.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- secret_type == "ssh_key"
- kwargs.strategy == "set_jms"
- name: Change SSH key
@@ -30,7 +30,7 @@
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ kwargs.exclusive }}"
when: account.secret_type == "ssh_key"
when: secret_type == "ssh_key"
- name: Refresh connection
ansible.builtin.meta: reset_connection
@@ -42,7 +42,7 @@
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
when: account.secret_type == "password"
when: secret_type == "password"
- name: Verify SSH key
ansible.builtin.ping:
@@ -51,4 +51,4 @@
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
when: account.secret_type == "ssh_key"
when: secret_type == "ssh_key"

View File

@@ -12,7 +12,7 @@ from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer
from assets.const import HostTypes
from common.utils import get_logger
from common.utils import get_logger, lazyproperty
from common.utils.file import encrypt_and_compress_zip_file
from common.utils.timezone import local_now_display
from users.models import User
@@ -28,23 +28,23 @@ class ChangeSecretManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.method_hosts_mapper = defaultdict(list)
self.secret_type = self.execution.snapshot.get('secret_type')
self.secret_type = self.execution.snapshot['secret_type']
self.secret_strategy = self.execution.snapshot.get(
'secret_strategy', SecretStrategy.custom
)
self.ssh_key_change_strategy = self.execution.snapshot.get(
'ssh_key_change_strategy', SSHKeyStrategy.add
)
self.account_ids = self.execution.snapshot['accounts']
self.snapshot_account_usernames = self.execution.snapshot['accounts']
self.name_recorder_mapper = {} # 做个映射,方便后面处理
@classmethod
def method_type(cls):
return AutomationTypes.change_secret
def get_kwargs(self, account, secret, secret_type):
def get_kwargs(self, account, secret):
kwargs = {}
if secret_type != SecretType.SSH_KEY:
if self.secret_type != SecretType.SSH_KEY:
return kwargs
kwargs['strategy'] = self.ssh_key_change_strategy
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
@@ -54,29 +54,18 @@ class ChangeSecretManager(AccountBasePlaybookManager):
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
return kwargs
def secret_generator(self, secret_type):
@lazyproperty
def secret_generator(self):
return SecretGenerator(
self.secret_strategy, secret_type,
self.secret_strategy, self.secret_type,
self.execution.snapshot.get('password_rules')
)
def get_secret(self, secret_type):
def get_secret(self):
if self.secret_strategy == SecretStrategy.custom:
return self.execution.snapshot['secret']
else:
return self.secret_generator(secret_type).get_secret()
def get_accounts(self, privilege_account):
if not privilege_account:
print(f'not privilege account')
return []
asset = privilege_account.asset
accounts = asset.accounts.exclude(username=privilege_account.username)
accounts = accounts.filter(id__in=self.account_ids)
if self.secret_type:
accounts = accounts.filter(secret_type=self.secret_type)
return accounts
return self.secret_generator.get_secret()
def host_callback(
self, host, asset=None, account=None,
@@ -89,10 +78,17 @@ class ChangeSecretManager(AccountBasePlaybookManager):
if host.get('error'):
return host
accounts = self.get_accounts(account)
accounts = asset.accounts.all()
if account:
accounts = accounts.exclude(username=account.username)
if '*' not in self.snapshot_account_usernames:
accounts = accounts.filter(username__in=self.snapshot_account_usernames)
accounts = accounts.filter(secret_type=self.secret_type)
if not accounts:
print('没有发现待改密账号: %s 用户ID: %s 类型: %s' % (
asset.name, self.account_ids, self.secret_type
print('没有发现待改密账号: %s 用户: %s 类型: %s' % (
asset.name, self.snapshot_account_usernames, self.secret_type
))
return []
@@ -101,16 +97,16 @@ class ChangeSecretManager(AccountBasePlaybookManager):
method_hosts = [h for h in method_hosts if h != host['name']]
inventory_hosts = []
records = []
host['secret_type'] = self.secret_type
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
print(f'Windows {asset} does not support ssh key push')
print(f'Windows {asset} does not support ssh key push \n')
return inventory_hosts
for account in accounts:
h = deepcopy(host)
secret_type = account.secret_type
h['name'] += '(' + account.username + ')'
new_secret = self.get_secret(secret_type)
new_secret = self.get_secret()
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
@@ -120,15 +116,15 @@ class ChangeSecretManager(AccountBasePlaybookManager):
self.name_recorder_mapper[h['name']] = recorder
private_key_path = None
if secret_type == SecretType.SSH_KEY:
if self.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(new_secret, path_dir)
new_secret = self.generate_public_key(new_secret)
h['kwargs'] = self.get_kwargs(account, new_secret, secret_type)
h['kwargs'] = self.get_kwargs(account, new_secret)
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': secret_type,
'secret_type': account.secret_type,
'secret': new_secret,
'private_key_path': private_key_path
}

View File

@@ -60,6 +60,4 @@ class GatherAccountsFilter:
if not run_method_name:
return info
if hasattr(self, f'{run_method_name}_filter'):
return getattr(self, f'{run_method_name}_filter')(info)
return info
return getattr(self, f'{run_method_name}_filter')(info)

View File

@@ -22,8 +22,8 @@ class GatherAccountsManager(AccountBasePlaybookManager):
self.host_asset_mapper[host['name']] = asset
return host
def filter_success_result(self, tp, result):
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
def filter_success_result(self, host, result):
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
return result
@staticmethod

View File

@@ -1,6 +1,9 @@
from copy import deepcopy
from django.db.models import QuerySet
from accounts.const import AutomationTypes, SecretType
from accounts.models import Account
from assets.const import HostTypes
from common.utils import get_logger
from ..base.manager import AccountBasePlaybookManager
@@ -16,6 +19,36 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
def method_type(cls):
return AutomationTypes.push_account
def create_nonlocal_accounts(self, accounts, snapshot_account_usernames, asset):
secret_type = self.secret_type
usernames = accounts.filter(secret_type=secret_type).values_list(
'username', flat=True
)
create_usernames = set(snapshot_account_usernames) - set(usernames)
create_account_objs = [
Account(
name=f'{username}-{secret_type}', username=username,
secret_type=secret_type, asset=asset,
)
for username in create_usernames
]
Account.objects.bulk_create(create_account_objs)
def get_accounts(self, privilege_account, accounts: QuerySet):
if not privilege_account:
print(f'not privilege account')
return []
snapshot_account_usernames = self.execution.snapshot['accounts']
if '*' in snapshot_account_usernames:
return accounts.exclude(username=privilege_account.username)
asset = privilege_account.asset
self.create_nonlocal_accounts(accounts, snapshot_account_usernames, asset)
accounts = asset.accounts.exclude(username=privilege_account.username).filter(
username__in=snapshot_account_usernames, secret_type=self.secret_type
)
return accounts
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super(ChangeSecretManager, self).host_callback(
host, asset=asset, account=account, automation=automation,
@@ -24,36 +57,34 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
if host.get('error'):
return host
accounts = self.get_accounts(account)
accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts)
inventory_hosts = []
host['secret_type'] = self.secret_type
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
msg = f'Windows {asset} does not support ssh key push'
msg = f'Windows {asset} does not support ssh key push \n'
print(msg)
return inventory_hosts
for account in accounts:
h = deepcopy(host)
secret_type = account.secret_type
h['name'] += '(' + account.username + ')'
if self.secret_type is None:
new_secret = account.secret
else:
new_secret = self.get_secret(secret_type)
new_secret = self.get_secret()
self.name_recorder_mapper[h['name']] = {
'account': account, 'new_secret': new_secret,
}
private_key_path = None
if secret_type == SecretType.SSH_KEY:
if self.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(new_secret, path_dir)
new_secret = self.generate_public_key(new_secret)
h['kwargs'] = self.get_kwargs(account, new_secret, secret_type)
h['kwargs'] = self.get_kwargs(account, new_secret)
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': secret_type,
'secret_type': account.secret_type,
'secret': new_secret,
'private_key_path': private_key_path
}
@@ -81,9 +112,9 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
logger.error("Pust account error: ", e)
def run(self, *args, **kwargs):
if self.secret_type and not self.check_secret():
if not self.check_secret():
return
super(ChangeSecretManager, self).run(*args, **kwargs)
super().run(*args, **kwargs)
# @classmethod
# def trigger_by_asset_create(cls, asset):

View File

@@ -25,15 +25,6 @@ class VerifyAccountManager(AccountBasePlaybookManager):
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
return path
@classmethod
def method_type(cls):
return AutomationTypes.verify_account
def get_accounts(self, privilege_account, accounts: QuerySet):
account_ids = self.execution.snapshot['accounts']
accounts = accounts.filter(id__in=account_ids)
return accounts
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super().host_callback(
host, asset=asset, account=account,
@@ -71,6 +62,16 @@ class VerifyAccountManager(AccountBasePlaybookManager):
inventory_hosts.append(h)
return inventory_hosts
@classmethod
def method_type(cls):
return AutomationTypes.verify_account
def get_accounts(self, privilege_account, accounts: QuerySet):
snapshot_account_usernames = self.execution.snapshot['accounts']
if '*' not in snapshot_account_usernames:
accounts = accounts.filter(username__in=snapshot_account_usernames)
return accounts
def on_host_success(self, host, result):
account = self.host_account_mapper.get(host)
account.set_connectivity(Connectivity.OK)

View File

@@ -1,6 +1,6 @@
from common.utils import get_logger
from accounts.const import AutomationTypes
from assets.automations.ping_gateway.manager import PingGatewayManager
from common.utils import get_logger
logger = get_logger(__name__)
@@ -16,6 +16,6 @@ class VerifyGatewayAccountManager(PingGatewayManager):
logger.info(">>> 开始执行测试网关账号可连接性任务")
def get_accounts(self, gateway):
account_ids = self.execution.snapshot['accounts']
accounts = gateway.accounts.filter(id__in=account_ids)
usernames = self.execution.snapshot['accounts']
accounts = gateway.accounts.filter(username__in=usernames)
return accounts

View File

@@ -1,69 +0,0 @@
# Generated by Django 3.2.16 on 2023-03-07 07:36
from django.db import migrations
from django.db.models import Q
def get_nodes_all_assets(apps, *nodes):
node_model = apps.get_model('assets', 'Node')
asset_model = apps.get_model('assets', 'Asset')
node_ids = set()
descendant_node_query = Q()
for n in nodes:
node_ids.add(n.id)
descendant_node_query |= Q(key__istartswith=f'{n.key}:')
if descendant_node_query:
_ids = node_model.objects.order_by().filter(descendant_node_query).values_list('id', flat=True)
node_ids.update(_ids)
return asset_model.objects.order_by().filter(nodes__id__in=node_ids).distinct()
def get_all_assets(apps, snapshot):
node_model = apps.get_model('assets', 'Node')
asset_model = apps.get_model('assets', 'Asset')
asset_ids = snapshot.get('assets', [])
node_ids = snapshot.get('nodes', [])
nodes = node_model.objects.filter(id__in=node_ids)
node_asset_ids = get_nodes_all_assets(apps, *nodes).values_list('id', flat=True)
asset_ids = set(list(asset_ids) + list(node_asset_ids))
return asset_model.objects.filter(id__in=asset_ids)
def migrate_account_usernames_to_ids(apps, schema_editor):
db_alias = schema_editor.connection.alias
execution_model = apps.get_model('accounts', 'AutomationExecution')
account_model = apps.get_model('accounts', 'Account')
executions = execution_model.objects.using(db_alias).all()
executions_update = []
for execution in executions:
snapshot = execution.snapshot
accounts = account_model.objects.none()
account_usernames = snapshot.get('accounts', [])
for asset in get_all_assets(apps, snapshot):
accounts = accounts | asset.accounts.all()
secret_type = snapshot.get('secret_type')
if secret_type:
ids = accounts.filter(
username__in=account_usernames,
secret_type=secret_type
).values_list('id', flat=True)
else:
ids = accounts.filter(
username__in=account_usernames
).values_list('id', flat=True)
snapshot['accounts'] = [str(_id) for _id in ids]
execution.snapshot = snapshot
executions_update.append(execution)
execution_model.objects.bulk_update(executions_update, ['snapshot'])
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_alter_gatheredaccount_options'),
]
operations = [
migrations.RunPython(migrate_account_usernames_to_ids),
]

View File

@@ -1,12 +1,11 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.db import fields
from common.db.models import JMSBaseModel
from accounts.const import (
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
)
from accounts.models import Account
from common.db import fields
from common.db.models import JMSBaseModel
from .base import AccountBaseAutomation
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
@@ -28,35 +27,18 @@ class ChangeSecretMixin(models.Model):
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
)
accounts: list[str] # account usernames
get_all_assets: callable # get all assets
class Meta:
abstract = True
def create_nonlocal_accounts(self, usernames, asset):
pass
def get_account_ids(self):
usernames = self.accounts
accounts = Account.objects.none()
for asset in self.get_all_assets():
self.create_nonlocal_accounts(usernames, asset)
accounts = accounts | asset.accounts.all()
account_ids = accounts.filter(
username__in=usernames, secret_type=self.secret_type
).values_list('id', flat=True)
return [str(_id) for _id in account_ids]
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'secret': self.secret,
'secret_type': self.secret_type,
'accounts': self.get_account_ids(),
'password_rules': self.password_rules,
'secret_strategy': self.secret_strategy,
'password_rules': self.password_rules,
'ssh_key_change_strategy': self.ssh_key_change_strategy,
})
return attr_json

View File

@@ -2,7 +2,6 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from accounts.const import AutomationTypes
from accounts.models import Account
from jumpserver.utils import has_valid_xpack_license
from .base import AccountBaseAutomation
from .change_secret import ChangeSecretMixin
@@ -15,21 +14,6 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
username = models.CharField(max_length=128, verbose_name=_('Username'))
action = models.CharField(max_length=16, verbose_name=_('Action'))
def create_nonlocal_accounts(self, usernames, asset):
secret_type = self.secret_type
account_usernames = asset.accounts.filter(secret_type=self.secret_type).values_list(
'username', flat=True
)
create_usernames = set(usernames) - set(account_usernames)
create_account_objs = [
Account(
name=f'{username}-{secret_type}', username=username,
secret_type=secret_type, asset=asset,
)
for username in create_usernames
]
Account.objects.bulk_create(create_account_objs)
def set_period_schedule(self):
pass

View File

@@ -12,7 +12,7 @@ from accounts.const import SecretType
from common.db import fields
from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger,
random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key
random_string, lazyproperty, parse_ssh_public_key_str
)
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
@@ -118,13 +118,7 @@ class BaseAccount(JMSOrgBaseModel):
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
# https://github.com/ansible/ansible-runner/issues/544
# ssh requires OpenSSH format keys to have a full ending newline.
# It does not require this for old-style PEM keys.
with open(key_path, 'w') as f:
f.write(self.secret)
if is_openssh_format_key(self.secret.encode('utf-8')):
f.write("\n")
self.private_key_obj.write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path

View File

@@ -81,7 +81,7 @@ class AccountAssetSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
if isinstance(data, dict):
i = data.get('id') or data.get('pk')
i = data.get('id')
else:
i = data
@@ -135,15 +135,8 @@ class AccountHistorySerializer(serializers.ModelSerializer):
class Meta:
model = Account.history.model
fields = [
'id', 'secret', 'secret_type', 'version', 'history_date',
'history_user'
]
fields = ['id', 'secret', 'secret_type', 'version', 'history_date', 'history_user']
read_only_fields = fields
extra_kwargs = {
'history_user': {'label': _('User')},
'history_date': {'label': _('Date')},
}
class AccountTaskSerializer(serializers.Serializer):

View File

@@ -23,10 +23,12 @@ def push_accounts_to_assets_task(account_ids):
task_name = gettext_noop("Push accounts to assets")
task_name = PushAccountAutomation.generate_unique_name(task_name)
task_snapshot = {
'accounts': [str(account.id) for account in accounts],
'assets': [str(account.asset_id) for account in accounts],
}
tp = AutomationTypes.push_account
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
for account in accounts:
task_snapshot = {
'secret': account.secret,
'secret_type': account.secret_type,
'accounts': [account.username],
'assets': [str(account.asset_id)],
}
tp = AutomationTypes.push_account
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)

View File

@@ -17,9 +17,9 @@ __all__ = [
def verify_connectivity_util(assets, tp, accounts, task_name):
if not assets or not accounts:
return
account_ids = [str(account.id) for account in accounts]
account_usernames = list(accounts.values_list('username', flat=True))
task_snapshot = {
'accounts': account_ids,
'accounts': account_usernames,
'assets': [str(asset.id) for asset in assets],
}
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)

View File

@@ -12,7 +12,8 @@ from django.utils.translation import gettext as _
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
from assets.automations.methods import platform_automation_methods
from common.utils import get_logger, lazyproperty, is_openssh_format_key, ssh_pubkey_gen
from common.utils import get_logger, lazyproperty
from common.utils import ssh_pubkey_gen, ssh_key_string_to_obj
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
logger = get_logger(__name__)
@@ -126,13 +127,7 @@ class BasePlaybookManager:
key_path = os.path.join(path_dir, key_name)
if not os.path.exists(key_path):
# https://github.com/ansible/ansible-runner/issues/544
# ssh requires OpenSSH format keys to have a full ending newline.
# It does not require this for old-style PEM keys.
with open(key_path, 'w') as f:
f.write(secret)
if is_openssh_format_key(secret.encode('utf-8')):
f.write("\n")
ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path

View File

@@ -1,35 +0,0 @@
__all__ = ['FormatAssetInfo']
class FormatAssetInfo:
def __init__(self, tp):
self.tp = tp
@staticmethod
def posix_format(info):
for cpu_model in info.get('cpu_model', []):
if cpu_model.endswith('GHz') or cpu_model.startswith("Intel"):
break
else:
cpu_model = ''
info['cpu_model'] = cpu_model[:48]
info['cpu_count'] = info.get('cpu_count', 0)
return info
def run(self, method_id_meta_mapper, info):
for k, v in info.items():
info[k] = v.strip() if isinstance(v, str) else v
run_method_name = None
for k, v in method_id_meta_mapper.items():
if self.tp not in v['type']:
continue
run_method_name = k.replace(f'{v["method"]}_', '')
if not run_method_name:
return info
if hasattr(self, f'{run_method_name}_format'):
return getattr(self, f'{run_method_name}_format')(info)
return info

View File

@@ -11,7 +11,7 @@
cpu_count: "{{ ansible_processor_count }}"
cpu_cores: "{{ ansible_processor_cores }}"
cpu_vcpus: "{{ ansible_processor_vcpus }}"
memory: "{{ ansible_memtotal_mb / 1024 | round(2) }}"
memory: "{{ ansible_memtotal_mb }}"
disk_total: "{{ (ansible_mounts | map(attribute='size_total') | sum / 1024 / 1024 / 1024) | round(2) }}"
distribution: "{{ ansible_distribution }}"
distribution_version: "{{ ansible_distribution_version }}"

View File

@@ -1,6 +1,5 @@
from assets.const import AutomationTypes
from common.utils import get_logger
from .format_asset_info import FormatAssetInfo
from assets.const import AutomationTypes
from ..base.manager import BasePlaybookManager
logger = get_logger(__name__)
@@ -20,16 +19,13 @@ class GatherFactsManager(BasePlaybookManager):
self.host_asset_mapper[host['name']] = asset
return host
def format_asset_info(self, tp, info):
info = FormatAssetInfo(tp).run(self.method_id_meta_mapper, info)
return info
def on_host_success(self, host, result):
info = result.get('debug', {}).get('res', {}).get('info', {})
asset = self.host_asset_mapper.get(host)
if asset and info:
info = self.format_asset_info(asset.type, info)
for k, v in info.items():
info[k] = v.strip() if isinstance(v, str) else v
asset.info = info
asset.save(update_fields=['info'])
asset.save()
else:
logger.error("Not found info: {}".format(host))

View File

@@ -1,12 +1,10 @@
from django.utils.translation import gettext_lazy as _
from .base import BaseType
class CloudTypes(BaseType):
PUBLIC = 'public', _('Public cloud')
PRIVATE = 'private', _('Private cloud')
K8S = 'k8s', _('Kubernetes')
PUBLIC = 'public', 'Public cloud'
PRIVATE = 'private', 'Private cloud'
K8S = 'k8s', 'Kubernetes'
@classmethod
def _get_base_constrains(cls) -> dict:

View File

@@ -1,5 +1,3 @@
from django.utils.translation import gettext_lazy as _
from .base import BaseType
GATEWAY_NAME = 'Gateway'
@@ -9,7 +7,7 @@ class HostTypes(BaseType):
LINUX = 'linux', 'Linux'
WINDOWS = 'windows', 'Windows'
UNIX = 'unix', 'Unix'
OTHER_HOST = 'other', _("Other")
OTHER_HOST = 'other', "Other"
@classmethod
def _get_base_constrains(cls) -> dict:

View File

@@ -108,7 +108,7 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel):
verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
info = models.JSONField(verbose_name=_('Info'), default=dict, blank=True) # 资产的一些信息,如 硬件信息
info = models.JSONField(verbose_name='Info', default=dict, blank=True) # 资产的一些信息,如 硬件信息
objects = AssetManager.from_queryset(AssetQuerySet)()

View File

@@ -26,13 +26,6 @@ __all__ = [
class AssetProtocolsSerializer(serializers.ModelSerializer):
port = serializers.IntegerField(required=False, allow_null=True, max_value=65535, min_value=1)
def to_file_representation(self, data):
return '{name}/{port}'.format(**data)
def to_file_internal_value(self, data):
name, port = data.split('/')
return {'name': name, 'port': port}
class Meta:
model = Protocol
fields = ['name', 'port']
@@ -71,8 +64,7 @@ class AssetAccountSerializer(
template = serializers.BooleanField(
default=False, label=_("Template"), write_only=True
)
name = serializers.CharField(max_length=128, required=False, label=_("Name"))
secret_type = serializers.CharField(max_length=64, default='password', label=_("Secret type"))
name = serializers.CharField(max_length=128, required=True, label=_("Name"))
class Meta:
model = Account
@@ -129,8 +121,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
labels = AssetLabelSerializer(many=True, required=False, label=_('Label'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=())
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Account'))
nodes_display = serializers.ListField(read_only=True, label=_("Node path"))
accounts = AssetAccountSerializer(many=True, required=False, write_only=True, label=_('Account'))
class Meta:
model = Asset
@@ -142,11 +133,11 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
'nodes_display', 'accounts'
]
read_only_fields = [
'category', 'type', 'connectivity', 'auto_info',
'category', 'type', 'connectivity',
'date_verified', 'created_by', 'date_created',
'auto_info',
]
fields = fields_small + fields_fk + fields_m2m + read_only_fields
fields_unexport = ['auto_info']
extra_kwargs = {
'auto_info': {'label': _('Auto info')},
'name': {'label': _("Name")},
@@ -159,7 +150,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
self._init_field_choices()
def _get_protocols_required_default(self):
platform = self._asset_platform
platform = self._initial_data_platform
platform_protocols = platform.protocols.all()
protocols_default = [p for p in platform_protocols if p.default]
protocols_required = [p for p in platform_protocols if p.required or p.primary]
@@ -215,22 +206,20 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
instance.nodes.set(nodes_to_set)
@property
def _asset_platform(self):
def _initial_data_platform(self):
if self.instance:
return self.instance.platform
platform_id = self.initial_data.get('platform')
if isinstance(platform_id, dict):
platform_id = platform_id.get('id') or platform_id.get('pk')
if not platform_id and self.instance:
platform = self.instance.platform
else:
platform = Platform.objects.filter(id=platform_id).first()
platform = Platform.objects.filter(id=platform_id).first()
if not platform:
raise serializers.ValidationError({'platform': _("Platform not exist")})
return platform
def validate_domain(self, value):
platform = self._asset_platform
platform = self._initial_data_platform
if platform.domain_enabled:
return value
else:
@@ -274,8 +263,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
@staticmethod
def accounts_create(accounts_data, asset):
if not accounts_data:
return
for data in accounts_data:
data['asset'] = asset
AssetAccountSerializer().create(data)

View File

@@ -1,25 +1,26 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from assets.models import Host
from .common import AssetSerializer
__all__ = ['HostInfoSerializer', 'HostSerializer']
class HostInfoSerializer(serializers.Serializer):
vendor = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Vendor'))
model = serializers.CharField(max_length=54, required=False, allow_blank=True, label=_('Model'))
sn = serializers.CharField(max_length=128, required=False, allow_blank=True, label=_('Serial number'))
cpu_model = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('CPU model'))
cpu_count = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU count'))
cpu_cores = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU cores'))
cpu_vcpus = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU vcpus'))
model = serializers.CharField(max_length=54, required=False, allow_blank=True, label=_('Model'))
sn = serializers.CharField(max_length=128, required=False, allow_blank=True, label=_('Serial number'))
cpu_model = serializers.ListField(child=serializers.CharField(max_length=64, allow_blank=True), required=False, label=_('CPU model'))
cpu_count = serializers.IntegerField(required=False, label=_('CPU count'))
cpu_cores = serializers.IntegerField(required=False, label=_('CPU cores'))
cpu_vcpus = serializers.IntegerField(required=False, label=_('CPU vcpus'))
memory = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('Memory'))
disk_total = serializers.CharField(max_length=1024, allow_blank=True, required=False, label=_('Disk total'))
distribution = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('OS'))
distribution_version = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS version'))
distribution_version = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS version'))
arch = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS arch'))
@@ -35,3 +36,5 @@ class HostSerializer(AssetSerializer):
'label': _("IP/Host")
},
}

View File

@@ -29,8 +29,7 @@ class LabelSerializer(BulkOrgResourceModelSerializer):
@classmethod
def setup_eager_loading(cls, queryset):
queryset = queryset.prefetch_related('assets') \
.annotate(asset_count=Count('assets'))
queryset = queryset.annotate(asset_count=Count('assets'))
return queryset

View File

@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from urllib.parse import urlencode
from urllib3.exceptions import MaxRetryError, LocationParseError
from kubernetes import client
from kubernetes.client import api_client
from kubernetes.client.api import core_v1_api
from kubernetes.client.exceptions import ApiException
from common.utils import get_logger
from common.exceptions import JMSException
from ..const import CloudTypes, Category
logger = get_logger(__file__)
@@ -17,8 +20,7 @@ class KubernetesClient:
self.token = token
self.proxy = proxy
@property
def api(self):
def get_api(self):
configuration = client.Configuration()
configuration.host = self.url
configuration.proxy = self.proxy
@@ -28,29 +30,64 @@ class KubernetesClient:
api = core_v1_api.CoreV1Api(c)
return api
def get_namespaces(self):
namespaces = []
resp = self.api.list_namespace()
for ns in resp.items:
namespaces.append(ns.metadata.name)
return namespaces
def get_namespace_list(self):
api = self.get_api()
namespace_list = []
for ns in api.list_namespace().items:
namespace_list.append(ns.metadata.name)
return namespace_list
def get_pods(self, namespace):
pods = []
resp = self.api.list_namespaced_pod(namespace)
for pd in resp.items:
pods.append(pd.metadata.name)
return pods
def get_services(self):
api = self.get_api()
ret = api.list_service_for_all_namespaces(watch=False)
for i in ret.items:
print("%s \t%s \t%s \t%s \t%s \n" % (
i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports))
def get_containers(self, namespace, pod_name):
containers = []
resp = self.api.read_namespaced_pod(pod_name, namespace)
for container in resp.spec.containers:
containers.append(container.name)
return containers
def get_pod_info(self, namespace, pod):
api = self.get_api()
resp = api.read_namespaced_pod(namespace=namespace, name=pod)
return resp
@staticmethod
def get_proxy_url(asset):
def get_pod_logs(self, namespace, pod):
api = self.get_api()
log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200)
return log_content
def get_pods(self):
api = self.get_api()
try:
ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3))
except LocationParseError as e:
logger.warning("Kubernetes API request url error: {}".format(e))
raise JMSException(code='k8s_tree_error', detail=e)
except MaxRetryError:
msg = "Kubernetes API request timeout"
logger.warning(msg)
raise JMSException(code='k8s_tree_error', detail=msg)
except ApiException as e:
if e.status == 401:
msg = "Kubernetes API request unauthorized"
logger.warning(msg)
else:
msg = e
logger.warning(msg)
raise JMSException(code='k8s_tree_error', detail=msg)
data = {}
for i in ret.items:
namespace = i.metadata.namespace
pod_info = {
'pod_name': i.metadata.name,
'containers': [j.name for j in i.spec.containers]
}
if namespace in data:
data[namespace].append(pod_info)
else:
data[namespace] = [pod_info, ]
return data
@classmethod
def get_proxy_url(cls, asset):
if not asset.domain:
return None
@@ -60,14 +97,11 @@ class KubernetesClient:
return f'{gateway.address}:{gateway.port}'
@classmethod
def run(cls, asset, secret, tp, *args):
def get_kubernetes_data(cls, asset, secret):
k8s_url = f'{asset.address}'
proxy_url = cls.get_proxy_url(asset)
k8s = cls(k8s_url, secret, proxy=proxy_url)
func_name = f'get_{tp}s'
if hasattr(k8s, func_name):
return getattr(k8s, func_name)(*args)
return []
return k8s.get_pods()
class KubernetesTree:
@@ -83,15 +117,17 @@ class KubernetesTree:
)
return node
def as_namespace_node(self, name, tp):
def as_namespace_node(self, name, tp, counts=0):
i = urlencode({'namespace': name})
pid = str(self.asset.id)
name = f'{name}({counts})'
node = self.create_tree_node(i, pid, name, tp, icon='cloud')
return node
def as_pod_tree_node(self, namespace, name, tp):
def as_pod_tree_node(self, namespace, name, tp, counts=0):
pid = urlencode({'namespace': namespace})
i = urlencode({'namespace': namespace, 'pod': name})
name = f'{name}({counts})'
node = self.create_tree_node(i, pid, name, tp, icon='cloud')
return node
@@ -126,26 +162,30 @@ class KubernetesTree:
def async_tree_node(self, namespace, pod):
tree = []
data = KubernetesClient.get_kubernetes_data(self.asset, self.secret)
if not data:
return tree
if pod:
tp = 'container'
containers = KubernetesClient.run(
self.asset, self.secret, tp, namespace, pod
)
for container in containers:
for container in next(
filter(
lambda x: x['pod_name'] == pod, data[namespace]
)
)['containers']:
container_node = self.as_container_tree_node(
namespace, pod, container, tp
namespace, pod, container, 'container'
)
tree.append(container_node)
elif namespace:
tp = 'pod'
pods = KubernetesClient.run(self.asset, self.secret, tp, namespace)
for pod in pods:
pod_node = self.as_pod_tree_node(namespace, pod, tp)
tree.append(pod_node)
for pod in data[namespace]:
pod_nodes = self.as_pod_tree_node(
namespace, pod['pod_name'], 'pod', len(pod['containers'])
)
tree.append(pod_nodes)
else:
tp = 'namespace'
namespaces = KubernetesClient.run(self.asset, self.secret, tp)
for namespace in namespaces:
namespace_node = self.as_namespace_node(namespace, tp)
for namespace, pods in data.items():
namespace_node = self.as_namespace_node(
namespace, 'namespace', len(pods)
)
tree.append(namespace_node)
return tree

View File

@@ -24,7 +24,7 @@ from orgs.mixins.api import RootOrgViewMixin
from perms.models import ActionChoices
from terminal.connect_methods import NativeClient, ConnectMethodUtil
from terminal.models import EndpointRule
from ..models import ConnectionToken, date_expired_default
from ..models import ConnectionToken
from ..serializers import (
ConnectionTokenSerializer, ConnectionTokenSecretSerializer,
SuperConnectionTokenSerializer, ConnectTokenAppletOptionSerializer
@@ -172,7 +172,6 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin):
get_object: callable
get_serializer: callable
perform_create: callable
validate_exchange_token: callable
@action(methods=['POST', 'GET'], detail=True, url_path='rdp-file')
def get_rdp_file(self, *args, **kwargs):
@@ -205,18 +204,6 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin):
instance.expire()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(methods=['POST'], detail=False)
def exchange(self, request, *args, **kwargs):
pk = request.data.get('id', None) or request.data.get('pk', None)
# 只能兑换自己使用的 Token
instance = get_object_or_404(ConnectionToken, pk=pk, user=request.user)
instance.id = None
self.validate_exchange_token(instance)
instance.date_expired = date_expired_default()
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data, status=status.HTTP_201_CREATED)
class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelViewSet):
filterset_fields = (
@@ -230,7 +217,6 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
'list': 'authentication.view_connectiontoken',
'retrieve': 'authentication.view_connectiontoken',
'create': 'authentication.add_connectiontoken',
'exchange': 'authentication.add_connectiontoken',
'expire': 'authentication.change_connectiontoken',
'get_rdp_file': 'authentication.add_connectiontoken',
'get_client_protocol_url': 'authentication.add_connectiontoken',
@@ -254,24 +240,10 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
user = self.get_user(serializer)
asset = data.get('asset')
account_name = data.get('account')
_data = self._validate(user, asset, account_name)
data.update(_data)
return serializer
def validate_exchange_token(self, token):
user = token.user
asset = token.asset
account_name = token.account
_data = self._validate(user, asset, account_name)
for k, v in _data.items():
setattr(token, k, v)
return token
def _validate(self, user, asset, account_name):
data = dict()
data['org_id'] = asset.org_id
data['user'] = user
data['value'] = random_string(16)
account = self._validate_perm(user, asset, account_name)
if account.has_secret:
data['input_secret'] = ''
@@ -285,7 +257,8 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
if ticket:
data['from_ticket'] = ticket
data['is_active'] = False
return data
return account
@staticmethod
def _validate_perm(user, asset, account_name):

View File

@@ -222,8 +222,7 @@ class ConnectionToken(JMSOrgBaseModel):
'secret_type': account.secret_type,
'secret': account.secret or self.input_secret,
'su_from': account.su_from,
'org_id': account.org_id,
'privileged': account.privileged
'org_id': account.org_id
}
return Account(**data)

View File

@@ -3,12 +3,13 @@
from typing import Callable
from django.utils.translation import ugettext as _
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from common.const.http import POST
__all__ = ['SuggestionMixin', 'RenderToJsonMixin']

View File

@@ -1,15 +1,11 @@
import abc
import codecs
import json
import re
from django.utils.translation import ugettext_lazy as _
import codecs
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from rest_framework.parsers import BaseParser
from rest_framework import status
from rest_framework.exceptions import ParseError, APIException
from rest_framework.parsers import BaseParser
from common.serializers.fields import ObjectRelatedField
from common.utils import get_logger
logger = get_logger(__file__)
@@ -22,11 +18,11 @@ class FileContentOverflowedError(APIException):
class BaseFileParser(BaseParser):
FILE_CONTENT_MAX_LENGTH = 1024 * 1024 * 10
serializer_cls = None
serializer_fields = None
obj_pattern = re.compile(r'^(.+)\(([a-z0-9-]+)\)$')
def check_content_length(self, meta):
content_length = int(meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0)))
@@ -78,7 +74,7 @@ class BaseFileParser(BaseParser):
return s.translate(trans_table)
@classmethod
def load_row(cls, row):
def process_row(cls, row):
"""
构建json数据前的行处理
"""
@@ -88,63 +84,33 @@ class BaseFileParser(BaseParser):
col = cls._replace_chinese_quote(col)
# 列表/字典转换
if isinstance(col, str) and (
(col.startswith('[') and col.endswith(']')) or
(col.startswith('[') and col.endswith(']'))
or
(col.startswith("{") and col.endswith("}"))
):
try:
col = json.loads(col)
except json.JSONDecodeError as e:
logger.error('Json load error: ', e)
logger.error('col: ', col)
col = json.loads(col)
new_row.append(col)
return new_row
def id_name_to_obj(self, v):
if not v or not isinstance(v, str):
return v
matched = self.obj_pattern.match(v)
if not matched:
return v
obj_name, obj_id = matched.groups()
if len(obj_id) < 36:
obj_id = int(obj_id)
return {'pk': obj_id, 'name': obj_name}
def parse_value(self, field, value):
if value == '-' and field and field.allow_null:
return None
elif hasattr(field, 'to_file_internal_value'):
value = field.to_file_internal_value(value)
elif isinstance(field, serializers.BooleanField):
value = value.lower() in ['true', '1', 'yes']
elif isinstance(field, serializers.ChoiceField):
value = value
elif isinstance(field, ObjectRelatedField):
if field.many:
value = [self.id_name_to_obj(v) for v in value]
else:
value = self.id_name_to_obj(value)
elif isinstance(field, serializers.ListSerializer):
value = [self.parse_value(field.child, v) for v in value]
elif isinstance(field, serializers.Serializer):
value = self.id_name_to_obj(value)
elif isinstance(field, serializers.ManyRelatedField):
value = [self.parse_value(field.child_relation, v) for v in value]
elif isinstance(field, serializers.ListField):
value = [self.parse_value(field.child, v) for v in value]
return value
def process_row_data(self, row_data):
"""
构建json数据后的行数据处理
"""
new_row = {}
new_row_data = {}
serializer_fields = self.serializer_fields
for k, v in row_data.items():
field = self.serializer_fields.get(k)
v = self.parse_value(field, v)
new_row[k] = v
return new_row
if type(v) in [list, dict, int, bool] or (isinstance(v, str) and k.strip() and v.strip()):
# 处理类似disk_info为字符串的'{}'的问题
if not isinstance(v, str) and isinstance(serializer_fields[k], serializers.CharField):
v = str(v)
# 处理 BooleanField 的问题, 导出是 'True', 'False'
if isinstance(v, str) and v.strip().lower() == 'true':
v = True
elif isinstance(v, str) and v.strip().lower() == 'false':
v = False
new_row_data[k] = v
return new_row_data
def generate_data(self, fields_name, rows):
data = []
@@ -152,7 +118,7 @@ class BaseFileParser(BaseParser):
# 空行不处理
if not any(row):
continue
row = self.load_row(row)
row = self.process_row(row)
row_data = dict(zip(fields_name, row))
row_data = self.process_row_data(row_data)
data.append(row_data)
@@ -173,6 +139,7 @@ class BaseFileParser(BaseParser):
raise ParseError('The resource does not support imports!')
self.check_content_length(meta)
try:
stream_data = self.get_stream_data(stream)
rows = self.generate_rows(stream_data)
@@ -181,7 +148,6 @@ class BaseFileParser(BaseParser):
# 给 `common.mixins.api.RenderToJsonMixin` 提供,暂时只能耦合
column_title_field_pairs = list(zip(column_titles, field_names))
column_title_field_pairs = [(k, v) for k, v in column_title_field_pairs if k and v]
if not hasattr(request, 'jms_context'):
request.jms_context = {}
request.jms_context['column_title_field_pairs'] = column_title_field_pairs
@@ -191,3 +157,4 @@ class BaseFileParser(BaseParser):
except Exception as e:
logger.error(e, exc_info=True)
raise ParseError(_('Parse file error: {}').format(e))

View File

@@ -1,17 +1,13 @@
import pyexcel
from django.utils.translation import gettext as _
from .base import BaseFileParser
class ExcelFileParser(BaseFileParser):
media_type = 'text/xlsx'
def generate_rows(self, stream_data):
try:
workbook = pyexcel.get_book(file_type='xlsx', file_content=stream_data)
except Exception:
raise Exception(_('Invalid excel file'))
workbook = pyexcel.get_book(file_type='xlsx', file_content=stream_data)
# 默认获取第一个工作表sheet
sheet = workbook.sheet_by_index(0)
rows = sheet.rows()

View File

@@ -1,11 +1,8 @@
import abc
from datetime import datetime
from rest_framework import serializers
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import encoders, json
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
from common.utils import get_logger
logger = get_logger(__file__)
@@ -41,27 +38,18 @@ class BaseFileRenderer(BaseRenderer):
def get_rendered_fields(self):
fields = self.serializer.fields
if self.template == 'import':
fields = [v for k, v in fields.items() if not v.read_only and k != "org_id" and k != 'id']
return [v for k, v in fields.items() if not v.read_only and k != "org_id" and k != 'id']
elif self.template == 'update':
fields = [v for k, v in fields.items() if not v.read_only and k != "org_id"]
return [v for k, v in fields.items() if not v.read_only and k != "org_id"]
else:
fields = [v for k, v in fields.items() if not v.write_only and k != "org_id"]
meta = getattr(self.serializer, 'Meta', None)
if meta:
fields_unexport = getattr(meta, 'fields_unexport', [])
fields = [v for v in fields if v.field_name not in fields_unexport]
return fields
return [v for k, v in fields.items() if not v.write_only and k != "org_id"]
@staticmethod
def get_column_titles(render_fields):
titles = []
for field in render_fields:
name = field.label
if field.required:
name = '*' + name
titles.append(name)
return titles
return [
'*{}'.format(field.label) if field.required else str(field.label)
for field in render_fields
]
def process_data(self, data):
results = data['results'] if 'results' in data else data
@@ -71,6 +59,7 @@ class BaseFileRenderer(BaseRenderer):
if self.template == 'import':
results = [results[0]] if results else results
else:
# 限制数据数量
results = results[:10000]
@@ -79,53 +68,17 @@ class BaseFileRenderer(BaseRenderer):
return results
@staticmethod
def to_id_name(value):
if value is None:
return '-'
pk = str(value.get('id', '') or value.get('pk', ''))
name = value.get('name') or value.get('display_name', '')
return '{}({})'.format(name, pk)
@staticmethod
def to_choice_name(value):
if value is None:
return '-'
value = value.get('value', '')
return value
def render_value(self, field, value):
if value is None:
value = '-'
elif hasattr(field, 'to_file_representation'):
value = field.to_file_representation(value)
elif isinstance(value, bool):
value = 'Yes' if value else 'No'
elif isinstance(field, LabeledChoiceField):
value = value.get('value', '')
elif isinstance(field, ObjectRelatedField):
if field.many:
value = [self.to_id_name(v) for v in value]
else:
value = self.to_id_name(value)
elif isinstance(field, serializers.ListSerializer):
value = [self.render_value(field.child, v) for v in value]
elif isinstance(field, serializers.Serializer) and value.get('id'):
value = self.to_id_name(value)
elif isinstance(field, serializers.ManyRelatedField):
value = [self.render_value(field.child_relation, v) for v in value]
elif isinstance(field, serializers.ListField):
value = [self.render_value(field.child, v) for v in value]
if not isinstance(value, str):
value = json.dumps(value, cls=encoders.JSONEncoder, ensure_ascii=False)
return str(value)
def generate_rows(self, data, render_fields):
def generate_rows(data, render_fields):
for item in data:
row = []
for field in render_fields:
value = item.get(field.field_name)
value = self.render_value(field, value)
if value is None:
value = ''
elif isinstance(value, dict):
value = json.dumps(value, ensure_ascii=False)
else:
value = str(value)
row.append(value)
yield row
@@ -148,9 +101,6 @@ class BaseFileRenderer(BaseRenderer):
def get_rendered_value(self):
raise NotImplementedError
def after_render(self):
pass
def render(self, data, accepted_media_type=None, renderer_context=None):
if data is None:
return bytes()
@@ -179,10 +129,11 @@ class BaseFileRenderer(BaseRenderer):
self.initial_writer()
self.write_column_titles(column_titles)
self.write_rows(rows)
self.after_render()
value = self.get_rendered_value()
except Exception as e:
logger.debug(e, exc_info=True)
value = 'Render error! ({})'.format(self.media_type).encode('utf-8')
return value
return value

View File

@@ -1,6 +1,6 @@
from openpyxl import Workbook
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
from openpyxl.writer.excel import save_virtual_workbook
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
from .base import BaseFileRenderer
@@ -19,26 +19,12 @@ class ExcelFileRenderer(BaseFileRenderer):
def write_row(self, row):
self.row_count += 1
self.ws.row_dimensions[self.row_count].height = 20
column_count = 0
for cell_value in row:
# 处理非法字符
column_count += 1
cell_value = ILLEGAL_CHARACTERS_RE.sub(r'', str(cell_value))
self.ws.cell(row=self.row_count, column=column_count, value=str(cell_value))
def after_render(self):
for col in self.ws.columns:
max_length = 0
column = col[0].column_letter
for cell in col:
if len(str(cell.value)) > max_length:
max_length = len(cell.value)
adjusted_width = (max_length + 2) * 1.0
adjusted_width = 300 if adjusted_width > 300 else adjusted_width
adjusted_width = 30 if adjusted_width < 30 else adjusted_width
self.ws.column_dimensions[column].width = adjusted_width
self.wb.save('/tmp/test.xlsx')
cell_value = ILLEGAL_CHARACTERS_RE.sub(r'', cell_value)
self.ws.cell(row=self.row_count, column=column_count, value=cell_value)
def get_rendered_value(self):
value = save_virtual_workbook(self.wb)

View File

@@ -114,28 +114,26 @@ class ES(object):
self._ensure_index_exists()
def _ensure_index_exists(self):
info = self.es.info()
version = info['version']['number'].split('.')[0]
if version == '6':
mappings = {'mappings': {'data': {'properties': self.properties}}}
else:
mappings = {'mappings': {'properties': self.properties}}
if self.is_index_by_date:
mappings['aliases'] = {
self.query_index: {}
}
try:
info = self.es.info()
version = info['version']['number'].split('.')[0]
if version == '6':
mappings = {'mappings': {'data': {'properties': self.properties}}}
self.es.indices.create(self.index, body=mappings)
return
except RequestError as e:
if e.error == 'resource_already_exists_exception':
logger.warning(e)
else:
mappings = {'mappings': {'properties': self.properties}}
if self.is_index_by_date:
mappings['aliases'] = {
self.query_index: {}
}
try:
self.es.indices.create(self.index, body=mappings)
except RequestError as e:
if e.error == 'resource_already_exists_exception':
logger.warning(e)
else:
logger.exception(e)
except Exception as e:
logger.error(e, exc_info=True)
logger.exception(e)
def make_data(self, data):
return []

View File

@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
#
import phonenumbers
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@@ -18,7 +17,6 @@ __all__ = [
"BitChoicesField",
"TreeChoicesField",
"LabeledMultipleChoiceField",
"PhoneField",
]
@@ -203,11 +201,3 @@ class BitChoicesField(TreeChoicesField):
value = self.to_internal_value(data)
self.run_validators(value)
return value
class PhoneField(serializers.CharField):
def to_representation(self, value):
if value:
phone = phonenumbers.parse(value, 'CN')
value = {'code': '+%s' % phone.country_code, 'phone': phone.national_number}
return value

View File

@@ -55,11 +55,9 @@ class BulkSerializerMixin(object):
# add update_lookup_field field back to validated data
# since super by default strips out read-only fields
# hence id will no longer be present in validated_data
if all([
isinstance(self.root, BulkListSerializer),
id_attr,
request_method in ('PUT', 'PATCH')
]):
if all((isinstance(self.root, BulkListSerializer),
id_attr,
request_method in ('PUT', 'PATCH'))):
id_field = self.fields.get("id") or self.fields.get('pk')
if data.get("id"):
id_value = id_field.to_internal_value(data.get("id"))
@@ -137,7 +135,7 @@ class BulkListSerializerMixin:
pk = item["pk"]
else:
raise ValidationError("id or pk not in data")
child = self.instance.get(pk=pk)
child = self.instance.get(id=pk)
self.child.instance = child
self.child.initial_data = item
# raw

View File

@@ -32,7 +32,7 @@ class Counter:
return self.counter == other.counter
def digest_sql_query():
def on_request_finished_logging_db_query(sender, **kwargs):
queries = connection.queries
counters = defaultdict(Counter)
table_queries = defaultdict(list)
@@ -79,9 +79,6 @@ def digest_sql_query():
counter.counter, counter.time, name)
)
def on_request_finished_logging_db_query(sender, **kwargs):
digest_sql_query()
on_request_finished_release_local(sender, **kwargs)

View File

@@ -98,7 +98,7 @@ def ssh_private_key_gen(private_key, password=None):
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
private_key = ssh_private_key_gen(private_key, password=password)
if not isinstance(private_key, _supported_paramiko_ssh_key_types):
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
raise IOError('Invalid private key')
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {

View File

@@ -2,15 +2,12 @@
#
import re
import phonenumbers
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _
from rest_framework.validators import (
UniqueTogetherValidator, ValidationError
)
from rest_framework import serializers
from phonenumbers.phonenumberutil import NumberParseException
from common.utils.strings import no_special_chars
@@ -45,14 +42,9 @@ class NoSpecialChars:
class PhoneValidator:
pattern = re.compile(r"^1[3456789]\d{9}$")
message = _('The mobile phone number format is incorrect')
def __call__(self, value):
try:
phone = phonenumbers.parse(value, 'CN')
valid = phonenumbers.is_valid_number(phone)
except NumberParseException:
valid = False
if not valid:
if not self.pattern.match(value):
raise serializers.ValidationError(self.message)

View File

@@ -214,7 +214,7 @@ class Config(dict):
'REDIS_DB_WS': 6,
'GLOBAL_ORG_DISPLAY_NAME': '',
'SITE_URL': 'http://127.0.0.1',
'SITE_URL': 'http://localhost:8080',
'USER_GUIDE_URL': '',
'ANNOUNCEMENT_ENABLED': True,
'ANNOUNCEMENT': {},

View File

@@ -20,7 +20,7 @@ default_context = {
'LOGIN_WECOM_logo_logout': static('img/login_wecom_logo.png'),
'LOGIN_DINGTALK_logo_logout': static('img/login_dingtalk_logo.png'),
'LOGIN_FEISHU_logo_logout': static('img/login_feishu_logo.png'),
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2023',
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2022',
'INTERFACE': default_interface,
}

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
#
from datetime import datetime
from functools import partial
from werkzeug.local import LocalProxy
from datetime import datetime
from django.conf import settings
from werkzeug.local import LocalProxy
from common.local import thread_local
@@ -35,7 +34,7 @@ def get_xpack_license_info() -> dict:
corporation = info.get('corporation', '')
else:
current_year = datetime.now().year
corporation = f'FIT2CLOUD 飞致云 © 2014-{current_year}'
corporation = f'Copyright - FIT2CLOUD 飞致云 © 2014-{current_year}'
info = {
'corporation': corporation
}

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6fa80b59b9b5f95a9cfcad8ec47eacd519bb962d139ab90463795a7b306a0a72
size 137935
oid sha256:8c2600b7094db2a9e64862169ff1c826d5064fae9b9e71744545a1cea88cbc65
size 136280

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-03-14 17:34+0800\n"
"POT-Creation-Date: 2023-03-02 16:00+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"
@@ -169,7 +169,7 @@ msgstr "作成のみ"
#: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19
#: assets/serializers/label.py:27 audits/models.py:48
#: authentication/models/connection_token.py:33
#: perms/models/asset_permission.py:64 perms/serializers/permission.py:35
#: perms/models/asset_permission.py:64 perms/serializers/permission.py:27
#: terminal/backends/command/models.py:20 terminal/models/session/session.py:32
#: terminal/notifications.py:95 terminal/serializers/command.py:17
#: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:212
@@ -182,7 +182,7 @@ msgid "Su from"
msgstr "から切り替え"
#: accounts/models/account.py:53 settings/serializers/auth/cas.py:20
#: settings/serializers/auth/feishu.py:20 terminal/models/applet/applet.py:29
#: terminal/models/applet/applet.py:29
msgid "Version"
msgstr "バージョン"
@@ -195,9 +195,9 @@ msgstr "ソース"
#: accounts/serializers/automations/change_secret.py:112
#: accounts/serializers/automations/change_secret.py:132
#: acls/models/base.py:102 acls/serializers/base.py:57
#: assets/serializers/asset/common.py:131 assets/serializers/gateway.py:28
#: assets/serializers/asset/common.py:124 assets/serializers/gateway.py:28
#: audits/models.py:49 ops/models/base.py:18
#: perms/models/asset_permission.py:70 perms/serializers/permission.py:40
#: perms/models/asset_permission.py:70 perms/serializers/permission.py:32
#: terminal/backends/command/models.py:21 terminal/models/session/session.py:34
#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85
msgid "Account"
@@ -236,7 +236,7 @@ msgid "Can change asset account template secret"
msgstr "アセット アカウント テンプレートのパスワードを変更できます"
#: accounts/models/automations/backup_account.py:27
#: accounts/models/automations/change_secret.py:65
#: accounts/models/automations/change_secret.py:47
#: accounts/serializers/account/backup.py:34
#: accounts/serializers/automations/change_secret.py:57
msgid "Recipient"
@@ -327,7 +327,7 @@ msgstr "プッシュ アカウントの実行を表示する"
msgid "Can add push account execution"
msgstr "プッシュ アカウントの作成の実行"
#: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36
#: accounts/models/automations/change_secret.py:17 accounts/models/base.py:36
#: accounts/serializers/account/account.py:134
#: accounts/serializers/account/base.py:16
#: accounts/serializers/automations/change_secret.py:46
@@ -336,52 +336,52 @@ msgstr "プッシュ アカウントの作成の実行"
msgid "Secret type"
msgstr "鍵の種類"
#: accounts/models/automations/change_secret.py:20
#: accounts/models/automations/change_secret.py:90 accounts/models/base.py:38
#: accounts/models/automations/change_secret.py:19
#: accounts/models/automations/change_secret.py:72 accounts/models/base.py:38
#: authentication/models/temp_token.py:10
#: authentication/templates/authentication/_access_key_modal.html:31
#: settings/serializers/auth/radius.py:19
msgid "Secret"
msgstr "ひみつ"
#: accounts/models/automations/change_secret.py:23
#: accounts/models/automations/change_secret.py:22
#: accounts/serializers/automations/change_secret.py:40
msgid "Secret strategy"
msgstr "鍵ポリシー"
#: accounts/models/automations/change_secret.py:25
#: accounts/models/automations/change_secret.py:24
msgid "Password rules"
msgstr "パスワードルール"
#: accounts/models/automations/change_secret.py:28
#: accounts/models/automations/change_secret.py:27
msgid "SSH key change strategy"
msgstr "SSHキープッシュ方式"
#: accounts/models/automations/change_secret.py:72
#: accounts/models/automations/change_secret.py:54
msgid "Change secret automation"
msgstr "自動暗号化"
#: accounts/models/automations/change_secret.py:89
#: accounts/models/automations/change_secret.py:71
msgid "Old secret"
msgstr "以前のパスワード"
#: accounts/models/automations/change_secret.py:91
#: accounts/models/automations/change_secret.py:73
msgid "Date started"
msgstr "開始日"
#: accounts/models/automations/change_secret.py:92
#: accounts/models/automations/change_secret.py:74
#: assets/models/automations/base.py:115 ops/models/base.py:56
#: ops/models/celery.py:64 ops/models/job.py:192
#: terminal/models/applet/host.py:110
msgid "Date finished"
msgstr "終了日"
#: accounts/models/automations/change_secret.py:94 assets/const/automation.py:8
#: accounts/models/automations/change_secret.py:76 assets/const/automation.py:8
#: common/const/choices.py:20
msgid "Error"
msgstr "間違い"
#: accounts/models/automations/change_secret.py:98
#: accounts/models/automations/change_secret.py:80
msgid "Change secret record"
msgstr "パスワード レコードの変更"
@@ -394,7 +394,7 @@ msgid "Date last login"
msgstr "最終ログイン日"
#: accounts/models/automations/gather_account.py:15
#: accounts/models/automations/push_account.py:15 accounts/models/base.py:34
#: accounts/models/automations/push_account.py:14 accounts/models/base.py:34
#: acls/serializers/base.py:18 acls/serializers/base.py:49
#: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25
#: authentication/forms.py:27 authentication/models/temp_token.py:9
@@ -419,11 +419,11 @@ msgstr "自動収集アカウント"
msgid "Gather asset accounts"
msgstr "アカウントのコレクション"
#: accounts/models/automations/push_account.py:14
#: accounts/models/automations/push_account.py:13
msgid "Triggers"
msgstr "トリガー方式"
#: accounts/models/automations/push_account.py:16 acls/models/base.py:81
#: accounts/models/automations/push_account.py:15 acls/models/base.py:81
#: acls/serializers/base.py:81 acls/serializers/login_acl.py:25
#: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82
#: authentication/serializers/connect_token_secret.py:109
@@ -431,7 +431,7 @@ msgstr "トリガー方式"
msgid "Action"
msgstr "アクション"
#: accounts/models/automations/push_account.py:59
#: accounts/models/automations/push_account.py:43
msgid "Push asset account"
msgstr "アカウントプッシュ"
@@ -446,7 +446,7 @@ msgstr "アカウントの確認"
#: assets/models/cmd_filter.py:21 assets/models/domain.py:18
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/models/platform.py:21 assets/models/platform.py:76
#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:151
#: assets/serializers/asset/common.py:67 assets/serializers/asset/common.py:143
#: assets/serializers/platform.py:133
#: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21
#: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57
@@ -470,7 +470,7 @@ msgstr "特権アカウント"
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:107
#: terminal/models/applet/applet.py:32 users/serializers/user.py:162
#: terminal/models/applet/applet.py:32 users/serializers/user.py:161
msgid "Is active"
msgstr "アクティブです。"
@@ -516,23 +516,23 @@ msgstr ""
"情報にアクセスしてください-> ファイル暗号化パスワードを設定してください"
#: accounts/serializers/account/account.py:65
#: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75
#: assets/serializers/asset/common.py:65 settings/serializers/auth/sms.py:75
msgid "Template"
msgstr "テンプレート"
#: accounts/serializers/account/account.py:68
#: assets/serializers/asset/common.py:69
#: assets/serializers/asset/common.py:62
msgid "Push now"
msgstr "今すぐプッシュ"
#: accounts/serializers/account/account.py:70
#: accounts/serializers/account/base.py:64
#: accounts/serializers/account/base.py:62
msgid "Has secret"
msgstr "エスクローされたパスワード"
#: accounts/serializers/account/account.py:75 applications/models.py:11
#: assets/models/label.py:21 assets/models/platform.py:77
#: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8
#: assets/serializers/asset/common.py:120 assets/serializers/cagegory.py:8
#: assets/serializers/platform.py:94 assets/serializers/platform.py:134
#: perms/serializers/user_permission.py:26 settings/models.py:35
#: tickets/models/ticket/apply_application.py:13
@@ -544,7 +544,7 @@ msgstr "カテゴリ"
#: acls/serializers/command_acl.py:18 applications/models.py:14
#: assets/models/_user.py:50 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:78
#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:93
#: assets/serializers/asset/common.py:121 assets/serializers/platform.py:93
#: audits/serializers.py:48
#: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31
@@ -591,8 +591,8 @@ msgstr "キー/パスワード"
msgid "Key password"
msgstr "キーパスワード"
#: accounts/serializers/account/base.py:81
#: assets/serializers/asset/common.py:301
#: accounts/serializers/account/base.py:79
#: assets/serializers/asset/common.py:291
msgid "Spec info"
msgstr "特別情報"
@@ -741,7 +741,7 @@ msgstr "アクティブ"
#: authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58
#: perms/serializers/permission.py:31 rbac/builtin.py:122
#: perms/serializers/permission.py:23 rbac/builtin.py:122
#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:32
#: terminal/notifications.py:96 terminal/notifications.py:144
@@ -828,7 +828,7 @@ msgstr ""
"192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:"
"db8:1a:1110:::/64 (ドメイン名サポート)"
#: acls/serializers/base.py:40 assets/serializers/asset/host.py:35
#: acls/serializers/base.py:40 assets/serializers/asset/host.py:36
msgid "IP/Host"
msgstr "IP/ホスト"
@@ -934,7 +934,7 @@ msgstr "削除に失敗し、ノードにアセットが含まれています。
msgid "App assets"
msgstr "アプリ資産"
#: assets/automations/base/manager.py:113
#: assets/automations/base/manager.py:114
msgid "{} disabled"
msgstr "{} 無効"
@@ -996,7 +996,7 @@ msgid "Device"
msgstr "インターネット機器"
#: assets/const/category.py:13 assets/models/asset/database.py:9
#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:115
#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:108
msgid "Database"
msgstr "データベース"
@@ -1009,18 +1009,6 @@ msgstr "クラウド サービス"
msgid "Web"
msgstr "Web"
#: assets/const/cloud.py:7
msgid "Public cloud"
msgstr "パブリック クラウド"
#: assets/const/cloud.py:8
msgid "Private cloud"
msgstr "私有雲"
#: assets/const/cloud.py:9
msgid "Kubernetes"
msgstr ""
#: assets/const/device.py:7 terminal/models/applet/applet.py:24
#: tickets/const.py:8
msgid "General"
@@ -1038,10 +1026,6 @@ msgstr "ルーター"
msgid "Firewall"
msgstr "ファイアウォール"
#: assets/const/host.py:12 rbac/tree.py:28
msgid "Other"
msgstr "その他"
#: assets/const/types.py:200
msgid "All types"
msgstr "いろんなタイプ"
@@ -1060,7 +1044,7 @@ msgid "Basic"
msgstr "基本"
#: assets/const/web.py:61 assets/models/asset/web.py:13
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:40
#: assets/serializers/asset/common.py:116 assets/serializers/platform.py:40
msgid "Script"
msgstr "脚本"
@@ -1189,7 +1173,7 @@ msgstr "クラウド サービス"
msgid "Port"
msgstr "ポート"
#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:152
#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:144
msgid "Address"
msgstr "アドレス"
@@ -1226,7 +1210,7 @@ msgstr "アセットを一致させることができます"
msgid "Can change asset nodes"
msgstr "資産ノードを変更できます"
#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:116
#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:109
#: settings/serializers/email.py:37
msgid "Use SSL"
msgstr "SSLの使用"
@@ -1243,7 +1227,7 @@ msgstr "クライアント証明書"
msgid "Client key"
msgstr "クライアントキー"
#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:117
#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:110
msgid "Allow invalid cert"
msgstr "証明書チェックを無視"
@@ -1251,23 +1235,23 @@ msgstr "証明書チェックを無視"
msgid "Autofill"
msgstr "自動充填"
#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:120
#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:113
#: assets/serializers/platform.py:32
msgid "Username selector"
msgstr "ユーザー名ピッカー"
#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:121
#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:114
#: assets/serializers/platform.py:35
msgid "Password selector"
msgstr "パスワードセレクター"
#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:122
#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:115
#: assets/serializers/platform.py:38
msgid "Submit selector"
msgstr "ボタンセレクターを確認する"
#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
#: assets/serializers/asset/common.py:300 rbac/tree.py:35
#: assets/serializers/asset/common.py:290 rbac/tree.py:35
msgid "Accounts"
msgstr "アカウント"
@@ -1283,7 +1267,7 @@ msgstr "アセットの自動化タスク"
#: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:183
#: terminal/models/applet/applet.py:157 terminal/models/applet/host.py:108
#: terminal/models/component/status.py:27 terminal/serializers/applet.py:18
#: terminal/serializers/applet_host.py:103 tickets/models/ticket/general.py:283
#: terminal/serializers/applet_host.py:93 tickets/models/ticket/general.py:283
#: tickets/serializers/super_ticket.py:13
#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:164
#: xpack/plugins/cloud/models.py:216
@@ -1307,7 +1291,7 @@ msgid "Date verified"
msgstr "確認済みの日付"
#: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61
#: perms/serializers/permission.py:33 users/models/group.py:25
#: perms/serializers/permission.py:25 users/models/group.py:25
#: users/models/user.py:723
msgid "User group"
msgstr "ユーザーグループ"
@@ -1369,7 +1353,7 @@ msgstr "システム"
msgid "Value"
msgstr "値"
#: assets/models/label.py:40 assets/serializers/asset/common.py:129
#: assets/models/label.py:40 assets/serializers/asset/common.py:122
#: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13
#: authentication/serializers/connect_token_secret.py:114
#: common/serializers/common.py:79 settings/serializers/sms.py:7
@@ -1396,7 +1380,7 @@ msgstr "フルバリュー"
msgid "Parent key"
msgstr "親キー"
#: assets/models/node.py:558 perms/serializers/permission.py:36
#: assets/models/node.py:558 perms/serializers/permission.py:28
#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96
msgid "Node"
msgstr "ノード"
@@ -1498,36 +1482,35 @@ msgstr "オートメーション"
msgid "%(value)s is not an even number"
msgstr "%(value)s は偶数ではありません"
#: assets/serializers/asset/common.py:119
#: assets/serializers/asset/common.py:112
msgid "Auto fill"
msgstr "自動充填"
#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:96
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:96
#: authentication/serializers/connect_token_secret.py:28
#: authentication/serializers/connect_token_secret.py:66
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99
msgid "Protocols"
msgstr "プロトコル"
#: assets/serializers/asset/common.py:132
#: assets/serializers/asset/common.py:153
msgid "Node path"
msgstr "ノードパスです"
#: assets/serializers/asset/common.py:150
#: assets/serializers/asset/common.py:302
#: assets/serializers/asset/common.py:142
#: assets/serializers/asset/common.py:292
msgid "Auto info"
msgstr "自動情報"
#: assets/serializers/asset/common.py:226
#: assets/serializers/asset/common.py:145
msgid "Node path"
msgstr "ノードパスです"
#: assets/serializers/asset/common.py:218
msgid "Platform not exist"
msgstr "プラットフォームが存在しません"
#: assets/serializers/asset/common.py:261
#: assets/serializers/asset/common.py:253
msgid "port out of range (1-65535)"
msgstr "ポート番号が範囲外です (1-65535)"
#: assets/serializers/asset/common.py:268
#: assets/serializers/asset/common.py:260
msgid "Protocol is required: {}"
msgstr "プロトコルが必要です: {}"
@@ -1539,56 +1522,56 @@ msgstr "プロトコルが必要です: {}"
msgid "This field is required."
msgstr "このフィールドは必須です。"
#: assets/serializers/asset/host.py:11
#: assets/serializers/asset/host.py:12
msgid "Vendor"
msgstr "ベンダー"
#: assets/serializers/asset/host.py:12
#: assets/serializers/asset/host.py:13
msgid "Model"
msgstr "モデル"
#: assets/serializers/asset/host.py:13 tickets/models/ticket/general.py:299
#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:299
msgid "Serial number"
msgstr "シリアル番号"
#: assets/serializers/asset/host.py:14
#: assets/serializers/asset/host.py:15
msgid "CPU model"
msgstr "CPU モデル"
#: assets/serializers/asset/host.py:15
#: assets/serializers/asset/host.py:16
msgid "CPU count"
msgstr "CPU カウント"
#: assets/serializers/asset/host.py:16
#: assets/serializers/asset/host.py:17
msgid "CPU cores"
msgstr "CPU カラー"
#: assets/serializers/asset/host.py:17
#: assets/serializers/asset/host.py:18
msgid "CPU vcpus"
msgstr "CPU 合計"
#: assets/serializers/asset/host.py:18
#: assets/serializers/asset/host.py:19
msgid "Memory"
msgstr "メモリ"
#: assets/serializers/asset/host.py:19
#: assets/serializers/asset/host.py:20
msgid "Disk total"
msgstr "ディスクの合計"
#: assets/serializers/asset/host.py:21
#: assets/serializers/asset/host.py:22
#: authentication/serializers/connect_token_secret.py:105
msgid "OS"
msgstr "OS"
#: assets/serializers/asset/host.py:22
#: assets/serializers/asset/host.py:23
msgid "OS version"
msgstr "システムバージョン"
#: assets/serializers/asset/host.py:23
#: assets/serializers/asset/host.py:24
msgid "OS arch"
msgstr "システムアーキテクチャ"
#: assets/serializers/asset/host.py:27
#: assets/serializers/asset/host.py:28
msgid "Info"
msgstr "情報"
@@ -1814,11 +1797,11 @@ msgstr "タスク"
msgid "-"
msgstr "-"
#: audits/handler.py:115
#: audits/handler.py:116
msgid "Yes"
msgstr "是"
#: audits/handler.py:115
#: audits/handler.py:116
msgid "No"
msgstr "否"
@@ -1963,8 +1946,7 @@ msgstr "企業微信"
#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:144
#: authentication/views/login.py:86 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:780
#: settings/serializers/auth/feishu.py:10 users/models/user.py:780
msgid "FeiShu"
msgstr "本を飛ばす"
@@ -1987,19 +1969,19 @@ msgstr "監査セッション タスク ログのクリーンアップ"
msgid "This action require verify your MFA"
msgstr "この操作には、MFAを検証する必要があります"
#: authentication/api/connection_token.py:295
#: authentication/api/connection_token.py:268
msgid "Account not found"
msgstr "アカウントが見つかりません"
#: authentication/api/connection_token.py:298
#: authentication/api/connection_token.py:271
msgid "Permission expired"
msgstr "承認の有効期限が切れています"
#: authentication/api/connection_token.py:310
#: authentication/api/connection_token.py:283
msgid "ACL action is reject"
msgstr "ACL アクションは拒否です"
#: authentication/api/connection_token.py:314
#: authentication/api/connection_token.py:287
msgid "ACL action is review"
msgstr "ACL アクションはレビューです"
@@ -2435,7 +2417,7 @@ msgstr "ユーザーなしまたは期限切れのユーザー"
msgid "No asset or inactive asset"
msgstr "アセットがないか、有効化されていないアセット"
#: authentication/models/connection_token.py:258
#: authentication/models/connection_token.py:257
msgid "Super connection token"
msgstr "スーパー接続トークン"
@@ -2494,16 +2476,16 @@ msgid "Ticket info"
msgstr "作業指示情報"
#: authentication/serializers/connection_token.py:20
#: perms/models/asset_permission.py:71 perms/serializers/permission.py:37
#: perms/serializers/permission.py:70
#: perms/models/asset_permission.py:71 perms/serializers/permission.py:29
#: perms/serializers/permission.py:60
#: tickets/models/ticket/apply_application.py:28
#: tickets/models/ticket/apply_asset.py:18
msgid "Actions"
msgstr "アクション"
#: authentication/serializers/connection_token.py:41
#: perms/serializers/permission.py:39 perms/serializers/permission.py:71
#: users/serializers/user.py:93 users/serializers/user.py:165
#: perms/serializers/permission.py:31 perms/serializers/permission.py:61
#: users/serializers/user.py:93 users/serializers/user.py:164
msgid "Is expired"
msgstr "期限切れです"
@@ -2522,9 +2504,9 @@ msgstr "メール"
msgid "The {} cannot be empty"
msgstr "{} 空にしてはならない"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:38
#: perms/serializers/permission.py:72 users/serializers/user.py:94
#: users/serializers/user.py:163
#: authentication/serializers/token.py:79 perms/serializers/permission.py:30
#: perms/serializers/permission.py:62 users/serializers/user.py:94
#: users/serializers/user.py:162
msgid "Is valid"
msgstr "有効です"
@@ -2555,7 +2537,7 @@ msgstr "表示"
#: authentication/templates/authentication/_access_key_modal.html:66
#: settings/serializers/security.py:39 users/models/user.py:601
#: users/serializers/profile.py:115
#: users/serializers/profile.py:115 users/templates/users/mfa_setting.html:61
#: users/templates/users/user_verify_mfa.html:36
msgid "Disable"
msgstr "無効化"
@@ -2606,7 +2588,7 @@ msgstr "コードエラー"
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:417
#: jumpserver/conf.py:416
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@@ -2840,11 +2822,11 @@ msgstr "{} 認証へのリダイレクト"
msgid "Please enable cookies and try again."
msgstr "クッキーを有効にして、もう一度お試しください。"
#: authentication/views/login.py:247
#: authentication/views/login.py:238
msgid "User email already exists ({})"
msgstr "ユーザー メールボックスは既に存在します ({})"
#: authentication/views/login.py:325
#: authentication/views/login.py:318
msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page"
@@ -2852,15 +2834,15 @@ msgstr ""
"<b>{}</b> 確認を待ちます。彼女/彼へのリンクをコピーすることもできます <br/>\n"
" このページを閉じないでください"
#: authentication/views/login.py:330
#: authentication/views/login.py:323
msgid "No ticket found"
msgstr "チケットが見つかりません"
#: authentication/views/login.py:366
#: authentication/views/login.py:359
msgid "Logout success"
msgstr "ログアウト成功"
#: authentication/views/login.py:367
#: authentication/views/login.py:360
msgid "Logout success, return login page"
msgstr "ログアウト成功、ログインページを返す"
@@ -2892,7 +2874,7 @@ msgstr "企業の微信からユーザーを取得できませんでした"
msgid "Please login with a password and then bind the WeCom"
msgstr "パスワードでログインしてからWeComをバインドしてください"
#: common/api/action.py:51
#: common/api/action.py:52
msgid "Request file format may be wrong"
msgstr "リクエストファイルの形式が間違っている可能性があります"
@@ -2987,20 +2969,14 @@ msgstr "オブジェクト"
msgid "Organization ID"
msgstr "組織 ID"
#: common/drf/parsers/base.py:21
#: common/drf/parsers/base.py:17
msgid "The file content overflowed (The maximum length `{}` bytes)"
msgstr "ファイルの内容がオーバーフローしました (最大長 '{}' バイト)"
#: common/drf/parsers/base.py:189
#: common/drf/parsers/base.py:159
msgid "Parse file error: {}"
msgstr "解析ファイルエラー: {}"
#: common/drf/parsers/excel.py:14
#, fuzzy
#| msgid "Invalid zip file"
msgid "Invalid excel file"
msgstr "zip ファイルが無効です"
#: common/exceptions.py:15
#, python-format
msgid "%s object does not exist."
@@ -3136,7 +3112,7 @@ msgstr "無効な IP"
msgid "Invalid address"
msgstr "無効なアドレス。"
#: common/utils/translate.py:45
#: common/utils/translate.py:42
#, python-format
msgid "Hello %s"
msgstr "こんにちは %s"
@@ -3166,11 +3142,11 @@ msgstr "選択項目のみエクスポート"
msgid "Export filtered: %s"
msgstr "検索のエクスポート: %s"
#: jumpserver/conf.py:416
#: jumpserver/conf.py:415
msgid "Create account successfully"
msgstr "アカウントを正常に作成"
#: jumpserver/conf.py:418
#: jumpserver/conf.py:417
msgid "Your account has been created successfully"
msgstr "アカウントが正常に作成されました"
@@ -3242,11 +3218,11 @@ msgstr "投稿サイトニュース"
msgid "No account available"
msgstr "利用可能なアカウントがありません"
#: ops/ansible/inventory.py:189
#: ops/ansible/inventory.py:186
msgid "Ansible disabled"
msgstr "Ansible 無効"
#: ops/ansible/inventory.py:205
#: ops/ansible/inventory.py:202
msgid "Skip hosts below:"
msgstr "次のホストをスキップします: "
@@ -3262,11 +3238,7 @@ msgstr "タスクは存在しません"
msgid "Task {} args or kwargs error"
msgstr "タスク実行パラメータエラー"
#: ops/api/playbook.py:38
msgid "Currently playbook is being used in a job"
msgstr "現在プレイブックは1つのジョブで使用されています"
#: ops/api/playbook.py:92
#: ops/api/playbook.py:83
msgid "Unsupported file content"
msgstr "サポートされていないファイルの内容"
@@ -3910,6 +3882,10 @@ msgstr "監査ビュー"
msgid "System setting"
msgstr "システム設定"
#: rbac/tree.py:28
msgid "Other"
msgstr "その他"
#: rbac/tree.py:37
msgid "Session audits"
msgstr "セッション監査"
@@ -4130,7 +4106,7 @@ msgstr "そうでない場合はユーザーを作成"
msgid "Enable DingTalk Auth"
msgstr "ピン認証の有効化"
#: settings/serializers/auth/feishu.py:16
#: settings/serializers/auth/feishu.py:14
msgid "Enable FeiShu Auth"
msgstr "飛本認証の有効化"
@@ -4181,12 +4157,12 @@ msgstr ""
"する方法、username, name,emailはjumpserverのユーザーが必要とする属性です"
#: settings/serializers/auth/ldap.py:77
msgid "Connect timeout (s)"
msgstr "接続タイムアウト (秒)"
msgid "Connect timeout"
msgstr "接続タイムアウト"
#: settings/serializers/auth/ldap.py:79
msgid "Search paged size (piece)"
msgstr "ページサイズを検索 (じょう)"
msgid "Search paged size"
msgstr "ページサイズを検索"
#: settings/serializers/auth/ldap.py:81
msgid "Enable LDAP auth"
@@ -4307,8 +4283,8 @@ msgid "Scopes"
msgstr "スコープ"
#: settings/serializers/auth/oidc.py:90
msgid "Id token max age (s)"
msgstr "IDトークンの最大年齢 (秒)"
msgid "Id token max age"
msgstr "IDトークンの最大年齢"
#: settings/serializers/auth/oidc.py:93
msgid "Id token include claims"
@@ -5494,7 +5470,7 @@ msgstr "無効なアプレット パッケージ、ファイル {} がありま
msgid "Hosting"
msgstr "ホスト マシン"
#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:53
#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:43
msgid "Deploy options"
msgstr "展開パラメーター"
@@ -5652,23 +5628,23 @@ msgstr "リプレイ"
msgid "Date end"
msgstr "終了日"
#: terminal/models/session/session.py:243
#: terminal/models/session/session.py:239
msgid "Session record"
msgstr "セッション記録"
#: terminal/models/session/session.py:245
#: terminal/models/session/session.py:241
msgid "Can monitor session"
msgstr "セッションを監視できます"
#: terminal/models/session/session.py:246
#: terminal/models/session/session.py:242
msgid "Can share session"
msgstr "セッションを共有できます"
#: terminal/models/session/session.py:247
#: terminal/models/session/session.py:243
msgid "Can terminate session"
msgstr "セッションを終了できます"
#: terminal/models/session/session.py:248
#: terminal/models/session/session.py:244
msgid "Can validate session action perm"
msgstr "セッションアクションのパーマを検証できます"
@@ -5744,58 +5720,35 @@ msgstr "セッションごと"
msgid "Per Device"
msgstr "デバイスごと"
#: terminal/serializers/applet_host.py:32
msgid "API Server"
msgstr "API 仕える"
#: terminal/serializers/applet_host.py:33
msgid "Core API"
msgstr "コア サービス アドレス"
msgid "RDS Licensing"
msgstr "RDS ライセンス"
#: terminal/serializers/applet_host.py:34
msgid ""
" \n"
" Tips: The application release machine communicates with the Core "
"service. \n"
" If the release machine and the Core service are on the same network "
"segment, \n"
" it is recommended to fill in the intranet address, otherwise fill in "
"the current site URL \n"
" <br> \n"
" eg: https://172.16.10.110 or https://dev.jumpserver.com\n"
" "
msgstr ""
"ヒント: アプリケーション リリース マシンは、コア サービスと通信します。リリー"
"ス マシンとコア サービスが同じネットワーク セグメント上にある場合は、イントラ"
"ネット アドレスを入力することをお勧めします。それ以外の場合は、現在のサイト "
"URL を入力します。<br>例: https://172.16.10.110 または https://dev."
"jumpserver.com"
#: terminal/serializers/applet_host.py:42 terminal/serializers/storage.py:168
msgid "Ignore Certificate Verification"
msgstr "証明書の検証を無視する"
#: terminal/serializers/applet_host.py:43
msgid "Existing RDS license"
msgstr "既存の RDS 証明書"
#: terminal/serializers/applet_host.py:44
msgid "RDS License Server"
msgstr "RDS ライセンス サーバー"
#: terminal/serializers/applet_host.py:45
#: terminal/serializers/applet_host.py:35
msgid "RDS Licensing Mode"
msgstr "RDS 認可モード"
#: terminal/serializers/applet_host.py:47
#: terminal/serializers/applet_host.py:37
msgid "RDS Single Session Per User"
msgstr "RDS シングル ユーザー シングル セッション"
#: terminal/serializers/applet_host.py:48
#: terminal/serializers/applet_host.py:38
msgid "RDS Max Disconnection Time"
msgstr "最大切断時間"
#: terminal/serializers/applet_host.py:49
#: terminal/serializers/applet_host.py:39
msgid "RDS Remote App Logoff Time Limit"
msgstr "RDS 远程应用注销时间限制"
#: terminal/serializers/applet_host.py:55 terminal/serializers/terminal.py:41
#: terminal/serializers/applet_host.py:45 terminal/serializers/terminal.py:41
msgid "Load status"
msgstr "ロードステータス"
@@ -5827,12 +5780,6 @@ msgstr ""
"Oracle プロキシサーバーがリッスンするポートは動的です。追加の Oracle データ"
"ベースインスタンスはポートリスナーを追加します"
#: terminal/serializers/endpoint.py:35
msgid "Visit IP/Host, if empty, use the current request instead"
msgstr ""
"IP/ホストにアクセスします。空の場合は、代わりに現在のリクエストのアドレスを使"
"用します"
#: terminal/serializers/endpoint.py:58
msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels"
@@ -5934,6 +5881,10 @@ msgstr "インデックス"
msgid "Doc type"
msgstr "Docタイプ"
#: terminal/serializers/storage.py:168
msgid "Ignore Certificate Verification"
msgstr "証明書の検証を無視する"
#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85
msgid "Not found"
msgstr "見つかりません"
@@ -6439,7 +6390,7 @@ msgstr "公開キー"
msgid "Force enable"
msgstr "強制有効"
#: users/models/user.py:729 users/serializers/user.py:164
#: users/models/user.py:729 users/serializers/user.py:163
msgid "Is service account"
msgstr "サービスアカウントです"
@@ -6468,7 +6419,7 @@ msgid "Secret key"
msgstr "秘密キー"
#: users/models/user.py:758 users/serializers/profile.py:147
#: users/serializers/user.py:161
#: users/serializers/user.py:160
msgid "Is first login"
msgstr "最初のログインです"
@@ -6575,7 +6526,7 @@ msgstr "MFAフォース有効化"
msgid "Login blocked"
msgstr "ログインブロック"
#: users/serializers/user.py:95 users/serializers/user.py:169
#: users/serializers/user.py:95 users/serializers/user.py:168
msgid "Is OTP bound"
msgstr "仮想MFAがバインドされているか"
@@ -6583,25 +6534,19 @@ msgstr "仮想MFAがバインドされているか"
msgid "Can public key authentication"
msgstr "公開鍵認証が可能"
#: users/serializers/user.py:166
#: users/serializers/user.py:165
msgid "Avatar url"
msgstr "アバターURL"
#: users/serializers/user.py:171
#, fuzzy
#| msgid "One level"
msgid "MFA level"
msgstr "1つのレベル"
#: users/serializers/user.py:277
#: users/serializers/user.py:275
msgid "Select users"
msgstr "ユーザーの選択"
#: users/serializers/user.py:278
#: users/serializers/user.py:276
msgid "For security, only list several users"
msgstr "セキュリティのために、複数のユーザーのみをリストします"
#: users/serializers/user.py:311
#: users/serializers/user.py:309
msgid "name not unique"
msgstr "名前が一意ではない"
@@ -6717,10 +6662,6 @@ msgstr "MFA強制有効化、無効化できません"
msgid "MFA setting"
msgstr "MFAの設定"
#: users/templates/users/mfa_setting.html:61
msgid "Reset"
msgstr "リセット"
#: users/templates/users/reset_password.html:23
msgid "Your password must satisfy"
msgstr "パスワードを満たす必要があります"

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9819889a6d8b2934b06c5b242e3f63f404997f30851919247a405f542e8a03bc
size 113244
oid sha256:a29193d2982b254444285cfb2d61f7ef7355ae2bab181cdf366446e879ab32fb
size 111963

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-03-14 17:34+0800\n"
"POT-Creation-Date: 2023-03-02 16:00+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"
@@ -168,7 +168,7 @@ msgstr "仅创建"
#: assets/models/cmd_filter.py:36 assets/serializers/domain.py:19
#: assets/serializers/label.py:27 audits/models.py:48
#: authentication/models/connection_token.py:33
#: perms/models/asset_permission.py:64 perms/serializers/permission.py:35
#: perms/models/asset_permission.py:64 perms/serializers/permission.py:27
#: terminal/backends/command/models.py:20 terminal/models/session/session.py:32
#: terminal/notifications.py:95 terminal/serializers/command.py:17
#: tickets/models/ticket/apply_asset.py:16 xpack/plugins/cloud/models.py:212
@@ -181,7 +181,7 @@ msgid "Su from"
msgstr "切换自"
#: accounts/models/account.py:53 settings/serializers/auth/cas.py:20
#: settings/serializers/auth/feishu.py:20 terminal/models/applet/applet.py:29
#: terminal/models/applet/applet.py:29
msgid "Version"
msgstr "版本"
@@ -194,9 +194,9 @@ msgstr "来源"
#: accounts/serializers/automations/change_secret.py:112
#: accounts/serializers/automations/change_secret.py:132
#: acls/models/base.py:102 acls/serializers/base.py:57
#: assets/serializers/asset/common.py:131 assets/serializers/gateway.py:28
#: assets/serializers/asset/common.py:124 assets/serializers/gateway.py:28
#: audits/models.py:49 ops/models/base.py:18
#: perms/models/asset_permission.py:70 perms/serializers/permission.py:40
#: perms/models/asset_permission.py:70 perms/serializers/permission.py:32
#: terminal/backends/command/models.py:21 terminal/models/session/session.py:34
#: tickets/models/ticket/command_confirm.py:13 xpack/plugins/cloud/models.py:85
msgid "Account"
@@ -235,7 +235,7 @@ msgid "Can change asset account template secret"
msgstr "可以更改资产账号模版密码"
#: accounts/models/automations/backup_account.py:27
#: accounts/models/automations/change_secret.py:65
#: accounts/models/automations/change_secret.py:47
#: accounts/serializers/account/backup.py:34
#: accounts/serializers/automations/change_secret.py:57
msgid "Recipient"
@@ -326,7 +326,7 @@ msgstr "查看推送账号执行"
msgid "Can add push account execution"
msgstr "创建推送账号执行"
#: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36
#: accounts/models/automations/change_secret.py:17 accounts/models/base.py:36
#: accounts/serializers/account/account.py:134
#: accounts/serializers/account/base.py:16
#: accounts/serializers/automations/change_secret.py:46
@@ -335,52 +335,52 @@ msgstr "创建推送账号执行"
msgid "Secret type"
msgstr "密文类型"
#: accounts/models/automations/change_secret.py:20
#: accounts/models/automations/change_secret.py:90 accounts/models/base.py:38
#: accounts/models/automations/change_secret.py:19
#: accounts/models/automations/change_secret.py:72 accounts/models/base.py:38
#: authentication/models/temp_token.py:10
#: authentication/templates/authentication/_access_key_modal.html:31
#: settings/serializers/auth/radius.py:19
msgid "Secret"
msgstr "密钥"
#: accounts/models/automations/change_secret.py:23
#: accounts/models/automations/change_secret.py:22
#: accounts/serializers/automations/change_secret.py:40
msgid "Secret strategy"
msgstr "密文策略"
#: accounts/models/automations/change_secret.py:25
#: accounts/models/automations/change_secret.py:24
msgid "Password rules"
msgstr "密码规则"
#: accounts/models/automations/change_secret.py:28
#: accounts/models/automations/change_secret.py:27
msgid "SSH key change strategy"
msgstr "SSH 密钥推送方式"
#: accounts/models/automations/change_secret.py:72
#: accounts/models/automations/change_secret.py:54
msgid "Change secret automation"
msgstr "自动化改密"
#: accounts/models/automations/change_secret.py:89
#: accounts/models/automations/change_secret.py:71
msgid "Old secret"
msgstr "原密码"
#: accounts/models/automations/change_secret.py:91
#: accounts/models/automations/change_secret.py:73
msgid "Date started"
msgstr "开始日期"
#: accounts/models/automations/change_secret.py:92
#: accounts/models/automations/change_secret.py:74
#: assets/models/automations/base.py:115 ops/models/base.py:56
#: ops/models/celery.py:64 ops/models/job.py:192
#: terminal/models/applet/host.py:110
msgid "Date finished"
msgstr "结束日期"
#: accounts/models/automations/change_secret.py:94 assets/const/automation.py:8
#: accounts/models/automations/change_secret.py:76 assets/const/automation.py:8
#: common/const/choices.py:20
msgid "Error"
msgstr "错误"
#: accounts/models/automations/change_secret.py:98
#: accounts/models/automations/change_secret.py:80
msgid "Change secret record"
msgstr "改密记录"
@@ -393,7 +393,7 @@ msgid "Date last login"
msgstr "最后登录日期"
#: accounts/models/automations/gather_account.py:15
#: accounts/models/automations/push_account.py:15 accounts/models/base.py:34
#: accounts/models/automations/push_account.py:14 accounts/models/base.py:34
#: acls/serializers/base.py:18 acls/serializers/base.py:49
#: assets/models/_user.py:23 audits/models.py:157 authentication/forms.py:25
#: authentication/forms.py:27 authentication/models/temp_token.py:9
@@ -418,11 +418,11 @@ msgstr "自动化收集账号"
msgid "Gather asset accounts"
msgstr "收集账号"
#: accounts/models/automations/push_account.py:14
#: accounts/models/automations/push_account.py:13
msgid "Triggers"
msgstr "触发方式"
#: accounts/models/automations/push_account.py:16 acls/models/base.py:81
#: accounts/models/automations/push_account.py:15 acls/models/base.py:81
#: acls/serializers/base.py:81 acls/serializers/login_acl.py:25
#: assets/models/cmd_filter.py:81 audits/models.py:65 audits/serializers.py:82
#: authentication/serializers/connect_token_secret.py:109
@@ -430,7 +430,7 @@ msgstr "触发方式"
msgid "Action"
msgstr "动作"
#: accounts/models/automations/push_account.py:59
#: accounts/models/automations/push_account.py:43
msgid "Push asset account"
msgstr "账号推送"
@@ -445,7 +445,7 @@ msgstr "账号验证"
#: assets/models/cmd_filter.py:21 assets/models/domain.py:18
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/models/platform.py:21 assets/models/platform.py:76
#: assets/serializers/asset/common.py:74 assets/serializers/asset/common.py:151
#: assets/serializers/asset/common.py:67 assets/serializers/asset/common.py:143
#: assets/serializers/platform.py:133
#: authentication/serializers/connect_token_secret.py:103 ops/mixin.py:21
#: ops/models/adhoc.py:21 ops/models/celery.py:15 ops/models/celery.py:57
@@ -469,7 +469,7 @@ msgstr "特权账号"
#: assets/models/automations/base.py:21 assets/models/cmd_filter.py:39
#: assets/models/label.py:22
#: authentication/serializers/connect_token_secret.py:107
#: terminal/models/applet/applet.py:32 users/serializers/user.py:162
#: terminal/models/applet/applet.py:32 users/serializers/user.py:161
msgid "Is active"
msgstr "激活"
@@ -512,23 +512,23 @@ msgstr ""
"密密码"
#: accounts/serializers/account/account.py:65
#: assets/serializers/asset/common.py:72 settings/serializers/auth/sms.py:75
#: assets/serializers/asset/common.py:65 settings/serializers/auth/sms.py:75
msgid "Template"
msgstr "模板"
#: accounts/serializers/account/account.py:68
#: assets/serializers/asset/common.py:69
#: assets/serializers/asset/common.py:62
msgid "Push now"
msgstr "立即推送"
#: accounts/serializers/account/account.py:70
#: accounts/serializers/account/base.py:64
#: accounts/serializers/account/base.py:62
msgid "Has secret"
msgstr "已托管密码"
#: accounts/serializers/account/account.py:75 applications/models.py:11
#: assets/models/label.py:21 assets/models/platform.py:77
#: assets/serializers/asset/common.py:127 assets/serializers/cagegory.py:8
#: assets/serializers/asset/common.py:120 assets/serializers/cagegory.py:8
#: assets/serializers/platform.py:94 assets/serializers/platform.py:134
#: perms/serializers/user_permission.py:26 settings/models.py:35
#: tickets/models/ticket/apply_application.py:13
@@ -540,7 +540,7 @@ msgstr "类别"
#: acls/serializers/command_acl.py:18 applications/models.py:14
#: assets/models/_user.py:50 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:78
#: assets/serializers/asset/common.py:128 assets/serializers/platform.py:93
#: assets/serializers/asset/common.py:121 assets/serializers/platform.py:93
#: audits/serializers.py:48
#: authentication/serializers/connect_token_secret.py:116 ops/models/job.py:102
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:31
@@ -587,8 +587,8 @@ msgstr "密钥/密码"
msgid "Key password"
msgstr "密钥密码"
#: accounts/serializers/account/base.py:81
#: assets/serializers/asset/common.py:301
#: accounts/serializers/account/base.py:79
#: assets/serializers/asset/common.py:291
msgid "Spec info"
msgstr "特殊信息"
@@ -737,7 +737,7 @@ msgstr "激活中"
#: authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:58
#: perms/serializers/permission.py:31 rbac/builtin.py:122
#: perms/serializers/permission.py:23 rbac/builtin.py:122
#: rbac/models/rolebinding.py:49 terminal/backends/command/models.py:19
#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:32
#: terminal/notifications.py:96 terminal/notifications.py:144
@@ -823,7 +823,7 @@ msgstr ""
"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, "
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
#: acls/serializers/base.py:40 assets/serializers/asset/host.py:35
#: acls/serializers/base.py:40 assets/serializers/asset/host.py:36
msgid "IP/Host"
msgstr "IP/主机"
@@ -926,7 +926,7 @@ msgstr "删除失败,节点包含资产"
msgid "App assets"
msgstr "资产管理"
#: assets/automations/base/manager.py:113
#: assets/automations/base/manager.py:114
msgid "{} disabled"
msgstr "{} 已禁用"
@@ -988,7 +988,7 @@ msgid "Device"
msgstr "网络设备"
#: assets/const/category.py:13 assets/models/asset/database.py:9
#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:115
#: assets/models/asset/database.py:24 assets/serializers/asset/common.py:108
msgid "Database"
msgstr "数据库"
@@ -1001,18 +1001,6 @@ msgstr "云服务"
msgid "Web"
msgstr "Web"
#: assets/const/cloud.py:7
msgid "Public cloud"
msgstr "公有云"
#: assets/const/cloud.py:8
msgid "Private cloud"
msgstr "私有云"
#: assets/const/cloud.py:9
msgid "Kubernetes"
msgstr ""
#: assets/const/device.py:7 terminal/models/applet/applet.py:24
#: tickets/const.py:8
msgid "General"
@@ -1030,10 +1018,6 @@ msgstr "路由器"
msgid "Firewall"
msgstr "防火墙"
#: assets/const/host.py:12 rbac/tree.py:28
msgid "Other"
msgstr "其它"
#: assets/const/types.py:200
msgid "All types"
msgstr "所有类型"
@@ -1052,7 +1036,7 @@ msgid "Basic"
msgstr "基本"
#: assets/const/web.py:61 assets/models/asset/web.py:13
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:40
#: assets/serializers/asset/common.py:116 assets/serializers/platform.py:40
msgid "Script"
msgstr "脚本"
@@ -1181,7 +1165,7 @@ msgstr "云服务"
msgid "Port"
msgstr "端口"
#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:152
#: assets/models/asset/common.py:103 assets/serializers/asset/common.py:144
msgid "Address"
msgstr "地址"
@@ -1218,7 +1202,7 @@ msgstr "可以匹配资产"
msgid "Can change asset nodes"
msgstr "可以修改资产节点"
#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:116
#: assets/models/asset/database.py:10 assets/serializers/asset/common.py:109
#: settings/serializers/email.py:37
msgid "Use SSL"
msgstr "使用 SSL"
@@ -1235,7 +1219,7 @@ msgstr "客户端证书"
msgid "Client key"
msgstr "客户端密钥"
#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:117
#: assets/models/asset/database.py:14 assets/serializers/asset/common.py:110
msgid "Allow invalid cert"
msgstr "忽略证书校验"
@@ -1243,23 +1227,23 @@ msgstr "忽略证书校验"
msgid "Autofill"
msgstr "自动代填"
#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:120
#: assets/models/asset/web.py:10 assets/serializers/asset/common.py:113
#: assets/serializers/platform.py:32
msgid "Username selector"
msgstr "用户名选择器"
#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:121
#: assets/models/asset/web.py:11 assets/serializers/asset/common.py:114
#: assets/serializers/platform.py:35
msgid "Password selector"
msgstr "密码选择器"
#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:122
#: assets/models/asset/web.py:12 assets/serializers/asset/common.py:115
#: assets/serializers/platform.py:38
msgid "Submit selector"
msgstr "确认按钮选择器"
#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
#: assets/serializers/asset/common.py:300 rbac/tree.py:35
#: assets/serializers/asset/common.py:290 rbac/tree.py:35
msgid "Accounts"
msgstr "账号管理"
@@ -1275,7 +1259,7 @@ msgstr "资产自动化任务"
#: audits/serializers.py:49 ops/models/base.py:49 ops/models/job.py:183
#: terminal/models/applet/applet.py:157 terminal/models/applet/host.py:108
#: terminal/models/component/status.py:27 terminal/serializers/applet.py:18
#: terminal/serializers/applet_host.py:103 tickets/models/ticket/general.py:283
#: terminal/serializers/applet_host.py:93 tickets/models/ticket/general.py:283
#: tickets/serializers/super_ticket.py:13
#: tickets/serializers/ticket/ticket.py:20 xpack/plugins/cloud/models.py:164
#: xpack/plugins/cloud/models.py:216
@@ -1299,7 +1283,7 @@ msgid "Date verified"
msgstr "校验日期"
#: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:61
#: perms/serializers/permission.py:33 users/models/group.py:25
#: perms/serializers/permission.py:25 users/models/group.py:25
#: users/models/user.py:723
msgid "User group"
msgstr "用户组"
@@ -1361,7 +1345,7 @@ msgstr "系统"
msgid "Value"
msgstr "值"
#: assets/models/label.py:40 assets/serializers/asset/common.py:129
#: assets/models/label.py:40 assets/serializers/asset/common.py:122
#: assets/serializers/cagegory.py:6 assets/serializers/cagegory.py:13
#: authentication/serializers/connect_token_secret.py:114
#: common/serializers/common.py:79 settings/serializers/sms.py:7
@@ -1388,7 +1372,7 @@ msgstr "全称"
msgid "Parent key"
msgstr "ssh私钥"
#: assets/models/node.py:558 perms/serializers/permission.py:36
#: assets/models/node.py:558 perms/serializers/permission.py:28
#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:96
msgid "Node"
msgstr "节点"
@@ -1490,36 +1474,35 @@ msgstr "自动化"
msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
#: assets/serializers/asset/common.py:119
#: assets/serializers/asset/common.py:112
msgid "Auto fill"
msgstr "自动代填"
#: assets/serializers/asset/common.py:130 assets/serializers/platform.py:96
#: assets/serializers/asset/common.py:123 assets/serializers/platform.py:96
#: authentication/serializers/connect_token_secret.py:28
#: authentication/serializers/connect_token_secret.py:66
#: perms/serializers/user_permission.py:25 xpack/plugins/cloud/models.py:99
msgid "Protocols"
msgstr "协议组"
#: assets/serializers/asset/common.py:132
#: assets/serializers/asset/common.py:153
msgid "Node path"
msgstr "节点路径"
#: assets/serializers/asset/common.py:150
#: assets/serializers/asset/common.py:302
#: assets/serializers/asset/common.py:142
#: assets/serializers/asset/common.py:292
msgid "Auto info"
msgstr "自动化信息"
#: assets/serializers/asset/common.py:226
#: assets/serializers/asset/common.py:145
msgid "Node path"
msgstr "节点路径"
#: assets/serializers/asset/common.py:218
msgid "Platform not exist"
msgstr "平台不存在"
#: assets/serializers/asset/common.py:261
#: assets/serializers/asset/common.py:253
msgid "port out of range (1-65535)"
msgstr "端口超出范围 (1-65535)"
#: assets/serializers/asset/common.py:268
#: assets/serializers/asset/common.py:260
msgid "Protocol is required: {}"
msgstr "协议是必填的: {}"
@@ -1531,56 +1514,56 @@ msgstr "协议是必填的: {}"
msgid "This field is required."
msgstr "该字段是必填项。"
#: assets/serializers/asset/host.py:11
#: assets/serializers/asset/host.py:12
msgid "Vendor"
msgstr "制造商"
#: assets/serializers/asset/host.py:12
#: assets/serializers/asset/host.py:13
msgid "Model"
msgstr "型号"
#: assets/serializers/asset/host.py:13 tickets/models/ticket/general.py:299
#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:299
msgid "Serial number"
msgstr "序列号"
#: assets/serializers/asset/host.py:14
#: assets/serializers/asset/host.py:15
msgid "CPU model"
msgstr "CPU型号"
#: assets/serializers/asset/host.py:15
#: assets/serializers/asset/host.py:16
msgid "CPU count"
msgstr "CPU数量"
#: assets/serializers/asset/host.py:16
#: assets/serializers/asset/host.py:17
msgid "CPU cores"
msgstr "CPU核数"
#: assets/serializers/asset/host.py:17
#: assets/serializers/asset/host.py:18
msgid "CPU vcpus"
msgstr "CPU总数"
#: assets/serializers/asset/host.py:18
#: assets/serializers/asset/host.py:19
msgid "Memory"
msgstr "内存"
#: assets/serializers/asset/host.py:19
#: assets/serializers/asset/host.py:20
msgid "Disk total"
msgstr "硬盘大小"
#: assets/serializers/asset/host.py:21
#: assets/serializers/asset/host.py:22
#: authentication/serializers/connect_token_secret.py:105
msgid "OS"
msgstr "操作系统"
#: assets/serializers/asset/host.py:22
#: assets/serializers/asset/host.py:23
msgid "OS version"
msgstr "系统版本"
#: assets/serializers/asset/host.py:23
#: assets/serializers/asset/host.py:24
msgid "OS arch"
msgstr "系统架构"
#: assets/serializers/asset/host.py:27
#: assets/serializers/asset/host.py:28
msgid "Info"
msgstr "信息"
@@ -1804,11 +1787,11 @@ msgstr "任务"
msgid "-"
msgstr "-"
#: audits/handler.py:115
#: audits/handler.py:116
msgid "Yes"
msgstr "是"
#: audits/handler.py:115
#: audits/handler.py:116
msgid "No"
msgstr "否"
@@ -1953,8 +1936,7 @@ msgstr "企业微信"
#: audits/signal_handlers/login_log.py:32 authentication/views/feishu.py:144
#: authentication/views/login.py:86 notifications/backends/__init__.py:14
#: settings/serializers/auth/feishu.py:10
#: settings/serializers/auth/feishu.py:13 users/models/user.py:780
#: settings/serializers/auth/feishu.py:10 users/models/user.py:780
msgid "FeiShu"
msgstr "飞书"
@@ -1977,19 +1959,19 @@ msgstr "清理审计会话任务日志"
msgid "This action require verify your MFA"
msgstr "该操作需要验证您的 MFA, 请先开启并配置"
#: authentication/api/connection_token.py:295
#: authentication/api/connection_token.py:268
msgid "Account not found"
msgstr "账号未找到"
#: authentication/api/connection_token.py:298
#: authentication/api/connection_token.py:271
msgid "Permission expired"
msgstr "授权已过期"
#: authentication/api/connection_token.py:310
#: authentication/api/connection_token.py:283
msgid "ACL action is reject"
msgstr "ACL 动作是拒绝"
#: authentication/api/connection_token.py:314
#: authentication/api/connection_token.py:287
msgid "ACL action is review"
msgstr "ACL 动作是复核"
@@ -2413,7 +2395,7 @@ msgstr "没有用户或用户失效"
msgid "No asset or inactive asset"
msgstr "没有资产或资产未激活"
#: authentication/models/connection_token.py:258
#: authentication/models/connection_token.py:257
msgid "Super connection token"
msgstr "超级连接令牌"
@@ -2472,16 +2454,16 @@ msgid "Ticket info"
msgstr "工单信息"
#: authentication/serializers/connection_token.py:20
#: perms/models/asset_permission.py:71 perms/serializers/permission.py:37
#: perms/serializers/permission.py:70
#: perms/models/asset_permission.py:71 perms/serializers/permission.py:29
#: perms/serializers/permission.py:60
#: tickets/models/ticket/apply_application.py:28
#: tickets/models/ticket/apply_asset.py:18
msgid "Actions"
msgstr "动作"
#: authentication/serializers/connection_token.py:41
#: perms/serializers/permission.py:39 perms/serializers/permission.py:71
#: users/serializers/user.py:93 users/serializers/user.py:165
#: perms/serializers/permission.py:31 perms/serializers/permission.py:61
#: users/serializers/user.py:93 users/serializers/user.py:164
msgid "Is expired"
msgstr "已过期"
@@ -2500,9 +2482,9 @@ msgstr "邮箱"
msgid "The {} cannot be empty"
msgstr "{} 不能为空"
#: authentication/serializers/token.py:79 perms/serializers/permission.py:38
#: perms/serializers/permission.py:72 users/serializers/user.py:94
#: users/serializers/user.py:163
#: authentication/serializers/token.py:79 perms/serializers/permission.py:30
#: perms/serializers/permission.py:62 users/serializers/user.py:94
#: users/serializers/user.py:162
msgid "Is valid"
msgstr "是否有效"
@@ -2533,7 +2515,7 @@ msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66
#: settings/serializers/security.py:39 users/models/user.py:601
#: users/serializers/profile.py:115
#: users/serializers/profile.py:115 users/templates/users/mfa_setting.html:61
#: users/templates/users/user_verify_mfa.html:36
msgid "Disable"
msgstr "禁用"
@@ -2584,7 +2566,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password_code.html:9
#: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:417
#: jumpserver/conf.py:416
#: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:33
@@ -2810,11 +2792,11 @@ msgstr "正在跳转到 {} 认证"
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:247
#: authentication/views/login.py:238
msgid "User email already exists ({})"
msgstr "用户邮箱已存在 ({})"
#: authentication/views/login.py:325
#: authentication/views/login.py:318
msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page"
@@ -2822,15 +2804,15 @@ msgstr ""
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
" 不要关闭本页面"
#: authentication/views/login.py:330
#: authentication/views/login.py:323
msgid "No ticket found"
msgstr "没有发现工单"
#: authentication/views/login.py:366
#: authentication/views/login.py:359
msgid "Logout success"
msgstr "退出登录成功"
#: authentication/views/login.py:367
#: authentication/views/login.py:360
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
@@ -2862,7 +2844,7 @@ msgstr "从企业微信获取用户失败"
msgid "Please login with a password and then bind the WeCom"
msgstr "请使用密码登录,然后绑定企业微信"
#: common/api/action.py:51
#: common/api/action.py:52
msgid "Request file format may be wrong"
msgstr "上传的文件格式错误 或 其它类型资源的文件"
@@ -2957,20 +2939,14 @@ msgstr "对象"
msgid "Organization ID"
msgstr "组织 ID"
#: common/drf/parsers/base.py:21
#: common/drf/parsers/base.py:17
msgid "The file content overflowed (The maximum length `{}` bytes)"
msgstr "文件内容太大 (最大长度 `{}` 字节)"
#: common/drf/parsers/base.py:189
#: common/drf/parsers/base.py:159
msgid "Parse file error: {}"
msgstr "解析文件错误: {}"
#: common/drf/parsers/excel.py:14
#, fuzzy
#| msgid "Invalid zip file"
msgid "Invalid excel file"
msgstr "无效的 zip 文件"
#: common/exceptions.py:15
#, python-format
msgid "%s object does not exist."
@@ -3106,7 +3082,7 @@ msgstr "无效 IP"
msgid "Invalid address"
msgstr "无效地址"
#: common/utils/translate.py:45
#: common/utils/translate.py:42
#, python-format
msgid "Hello %s"
msgstr "你好 %s"
@@ -3136,11 +3112,11 @@ msgstr "仅导出选择项"
msgid "Export filtered: %s"
msgstr "导出搜素: %s"
#: jumpserver/conf.py:416
#: jumpserver/conf.py:415
msgid "Create account successfully"
msgstr "创建账号成功"
#: jumpserver/conf.py:418
#: jumpserver/conf.py:417
msgid "Your account has been created successfully"
msgstr "你的账号已创建成功"
@@ -3207,11 +3183,11 @@ msgstr "发布站内消息"
msgid "No account available"
msgstr "无可用账号"
#: ops/ansible/inventory.py:189
#: ops/ansible/inventory.py:186
msgid "Ansible disabled"
msgstr "Ansible 已禁用"
#: ops/ansible/inventory.py:205
#: ops/ansible/inventory.py:202
msgid "Skip hosts below:"
msgstr "跳过以下主机: "
@@ -3227,11 +3203,7 @@ msgstr "任务 {} 不存在"
msgid "Task {} args or kwargs error"
msgstr "任务 {} 执行参数错误"
#: ops/api/playbook.py:38
msgid "Currently playbook is being used in a job"
msgstr "当前 playbook 正在作业中使用"
#: ops/api/playbook.py:92
#: ops/api/playbook.py:83
msgid "Unsupported file content"
msgstr "不支持的文件内容"
@@ -3873,6 +3845,10 @@ msgstr "审计台"
msgid "System setting"
msgstr "系统设置"
#: rbac/tree.py:28
msgid "Other"
msgstr "其它"
#: rbac/tree.py:37
msgid "Session audits"
msgstr "会话审计"
@@ -4093,7 +4069,7 @@ msgstr "创建用户(如果不存在)"
msgid "Enable DingTalk Auth"
msgstr "启用钉钉认证"
#: settings/serializers/auth/feishu.py:16
#: settings/serializers/auth/feishu.py:14
msgid "Enable FeiShu Auth"
msgstr "启用飞书认证"
@@ -4144,12 +4120,12 @@ msgstr ""
"email 是jumpserver的用户需要属性"
#: settings/serializers/auth/ldap.py:77
msgid "Connect timeout (s)"
msgstr "连接超时时间 (秒)"
msgid "Connect timeout"
msgstr "连接超时时间"
#: settings/serializers/auth/ldap.py:79
msgid "Search paged size (piece)"
msgstr "搜索分页数量 (条)"
msgid "Search paged size"
msgstr "搜索分页数量"
#: settings/serializers/auth/ldap.py:81
msgid "Enable LDAP auth"
@@ -4270,8 +4246,8 @@ msgid "Scopes"
msgstr "连接范围"
#: settings/serializers/auth/oidc.py:90
msgid "Id token max age (s)"
msgstr "令牌有效时间 (秒)"
msgid "Id token max age"
msgstr "令牌有效时间"
#: settings/serializers/auth/oidc.py:93
msgid "Id token include claims"
@@ -5422,7 +5398,7 @@ msgstr "Applet pkg 无效,缺少文件 {}"
msgid "Hosting"
msgstr "宿主机"
#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:53
#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:43
msgid "Deploy options"
msgstr "部署参数"
@@ -5580,23 +5556,23 @@ msgstr "回放"
msgid "Date end"
msgstr "结束日期"
#: terminal/models/session/session.py:243
#: terminal/models/session/session.py:239
msgid "Session record"
msgstr "会话记录"
#: terminal/models/session/session.py:245
#: terminal/models/session/session.py:241
msgid "Can monitor session"
msgstr "可以监控会话"
#: terminal/models/session/session.py:246
#: terminal/models/session/session.py:242
msgid "Can share session"
msgstr "可以分享会话"
#: terminal/models/session/session.py:247
#: terminal/models/session/session.py:243
msgid "Can terminate session"
msgstr "可以终断会话"
#: terminal/models/session/session.py:248
#: terminal/models/session/session.py:244
msgid "Can validate session action perm"
msgstr "可以验证会话动作权限"
@@ -5672,56 +5648,35 @@ msgstr "每用户"
msgid "Per Device"
msgstr "每设备"
#: terminal/serializers/applet_host.py:32
msgid "API Server"
msgstr "API 服务"
#: terminal/serializers/applet_host.py:33
msgid "Core API"
msgstr "Core 服务地址"
msgid "RDS Licensing"
msgstr "RDS 许可证"
#: terminal/serializers/applet_host.py:34
msgid ""
" \n"
" Tips: The application release machine communicates with the Core "
"service. \n"
" If the release machine and the Core service are on the same network "
"segment, \n"
" it is recommended to fill in the intranet address, otherwise fill in "
"the current site URL \n"
" <br> \n"
" eg: https://172.16.10.110 or https://dev.jumpserver.com\n"
" "
msgstr ""
"提示:应用发布机和 Core 服务进行通信使用,如果发布机和 Core 服务在同一网段,"
"建议填写内网地址,否则填写当前站点 URL<br>例如https://172.16.10.110 or "
"https://dev.jumpserver.com"
#: terminal/serializers/applet_host.py:42 terminal/serializers/storage.py:168
msgid "Ignore Certificate Verification"
msgstr "忽略证书认证"
#: terminal/serializers/applet_host.py:43
msgid "Existing RDS license"
msgstr "已有 RDS 许可证"
#: terminal/serializers/applet_host.py:44
msgid "RDS License Server"
msgstr "RDS 许可服务器"
#: terminal/serializers/applet_host.py:45
#: terminal/serializers/applet_host.py:35
msgid "RDS Licensing Mode"
msgstr "RDS 授权模式"
#: terminal/serializers/applet_host.py:47
#: terminal/serializers/applet_host.py:37
msgid "RDS Single Session Per User"
msgstr "RDS 单用户单会话"
#: terminal/serializers/applet_host.py:48
#: terminal/serializers/applet_host.py:38
msgid "RDS Max Disconnection Time"
msgstr "RDS 最大断开时间"
#: terminal/serializers/applet_host.py:49
#: terminal/serializers/applet_host.py:39
msgid "RDS Remote App Logoff Time Limit"
msgstr "RDS 远程应用注销时间限制"
#: terminal/serializers/applet_host.py:55 terminal/serializers/terminal.py:41
#: terminal/serializers/applet_host.py:45 terminal/serializers/terminal.py:41
msgid "Load status"
msgstr "负载状态"
@@ -5753,10 +5708,6 @@ msgstr ""
"Oracle 代理服务器监听端口是动态的,每增加一个 Oracle 数据库实例,就会增加一个"
"端口监听"
#: terminal/serializers/endpoint.py:35
msgid "Visit IP/Host, if empty, use the current request instead"
msgstr "访问IP/Host如果为空则使用当前请求的地址代替"
#: terminal/serializers/endpoint.py:58
msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels"
@@ -5856,6 +5807,10 @@ msgstr "索引"
msgid "Doc type"
msgstr "文档类型"
#: terminal/serializers/storage.py:168
msgid "Ignore Certificate Verification"
msgstr "忽略证书认证"
#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85
msgid "Not found"
msgstr "没有发现"
@@ -6355,7 +6310,7 @@ msgstr "SSH公钥"
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:729 users/serializers/user.py:164
#: users/models/user.py:729 users/serializers/user.py:163
msgid "Is service account"
msgstr "服务账号"
@@ -6384,7 +6339,7 @@ msgid "Secret key"
msgstr "Secret key"
#: users/models/user.py:758 users/serializers/profile.py:147
#: users/serializers/user.py:161
#: users/serializers/user.py:160
msgid "Is first login"
msgstr "首次登录"
@@ -6491,33 +6446,27 @@ msgstr "强制 MFA"
msgid "Login blocked"
msgstr "登录被阻塞"
#: users/serializers/user.py:95 users/serializers/user.py:169
#: users/serializers/user.py:95 users/serializers/user.py:168
msgid "Is OTP bound"
msgstr "是否绑定了虚拟 MFA"
#: users/serializers/user.py:97
msgid "Can public key authentication"
msgstr "可以使用公钥认证"
msgstr "公钥认证"
#: users/serializers/user.py:166
#: users/serializers/user.py:165
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers/user.py:171
#, fuzzy
#| msgid "One level"
msgid "MFA level"
msgstr "1 级"
#: users/serializers/user.py:277
#: users/serializers/user.py:275
msgid "Select users"
msgstr "选择用户"
#: users/serializers/user.py:278
#: users/serializers/user.py:276
msgid "For security, only list several users"
msgstr "为了安全,仅列出几个用户"
#: users/serializers/user.py:311
#: users/serializers/user.py:309
msgid "name not unique"
msgstr "名称重复"
@@ -6631,10 +6580,6 @@ msgstr "MFA 已强制启用,无法禁用"
msgid "MFA setting"
msgstr "设置 MFA 多因子认证"
#: users/templates/users/mfa_setting.html:61
msgid "Reset"
msgstr "重置"
#: users/templates/users/reset_password.html:23
msgid "Your password must satisfy"
msgstr "您的密码必须满足:"
@@ -7351,9 +7296,6 @@ msgstr "旗舰版"
msgid "Community edition"
msgstr "社区版"
#~ msgid "API Server"
#~ msgstr "API 服务"
#~ msgid "Unix admin user"
#~ msgstr "Unix 管理员"

View File

@@ -17,7 +17,7 @@ class JMSInventory:
:param account_policy: privileged_only, privileged_first, skip
"""
self.assets = self.clean_assets(assets)
self.account_prefer = self.get_account_prefer(account_prefer)
self.account_prefer = account_prefer
self.account_policy = account_policy
self.host_callback = host_callback
self.exclude_hosts = {}
@@ -139,47 +139,37 @@ class JMSInventory:
self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway)
return host
def get_asset_sorted_accounts(self, asset):
accounts = list(asset.accounts.filter(is_active=True))
connectivity_score = {'ok': 2, '-': 1, 'err': 0}
sort_key = lambda x: (x.privileged, connectivity_score.get(x.connectivity, 0), x.date_updated)
accounts_sorted = sorted(accounts, key=sort_key, reverse=True)
return accounts_sorted
@staticmethod
def get_account_prefer(account_prefer):
account_usernames = []
if isinstance(account_prefer, str) and account_prefer:
account_usernames = list(map(lambda x: x.lower(), account_prefer.split(',')))
return account_usernames
def get_refer_account(self, accounts):
account = None
if accounts:
account = list(filter(
lambda a: a.username.lower() in self.account_prefer, accounts
))
account = account[0] if account else None
return account
def get_asset_accounts(self, asset):
return list(asset.accounts.filter(is_active=True))
def select_account(self, asset):
accounts = self.get_asset_sorted_accounts(asset)
accounts = self.get_asset_accounts(asset)
if not accounts:
return None
account_selected = None
account_usernames = self.account_prefer
refer_account = self.get_refer_account(accounts)
if refer_account:
return refer_account
if isinstance(self.account_prefer, str):
account_usernames = self.account_prefer.split(',')
account_selected = accounts[0]
if self.account_policy == 'skip':
return None
elif self.account_policy == 'privileged_first':
# 优先使用提供的名称
if account_usernames:
account_matched = list(filter(lambda account: account.username in account_usernames, accounts))
account_selected = account_matched[0] if account_matched else None
if account_selected or self.account_policy == 'skip':
return account_selected
elif self.account_policy == 'privileged_only' and account_selected.privileged:
if self.account_policy in ['privileged_only', 'privileged_first']:
account_matched = list(filter(lambda account: account.privileged, accounts))
account_selected = account_matched[0] if account_matched else None
if account_selected:
return account_selected
else:
return None
if self.account_policy == 'privileged_first':
account_selected = accounts[0] if accounts else None
return account_selected
def generate(self, path_dir):
hosts = []

View File

@@ -83,7 +83,7 @@ class CeleryResultApi(generics.RetrieveAPIView):
def get_object(self):
pk = self.kwargs.get('pk')
return AsyncResult(str(pk))
return AsyncResult(pk)
class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):

View File

@@ -32,15 +32,6 @@ class PlaybookViewSet(OrgBulkModelViewSet):
model = Playbook
search_fields = ('name', 'comment')
def perform_destroy(self, instance):
instance = self.get_object()
if instance.job_set.exists():
raise JMSException(code='playbook_has_job', detail={"msg": _("Currently playbook is being used in a job")})
instance_id = instance.id
super().perform_destroy(instance)
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance_id.__str__())
shutil.rmtree(dest_path)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(creator=self.request.user)

View File

@@ -46,9 +46,8 @@ class JMSPermedInventory(JMSInventory):
self.user = user
self.assets_accounts_mapper = self.get_assets_accounts_mapper()
def get_asset_sorted_accounts(self, asset):
accounts = self.assets_accounts_mapper.get(asset.id, [])
return list(accounts)
def get_asset_accounts(self, asset):
return self.assets_accounts_mapper.get(asset.id, [])
def get_assets_accounts_mapper(self):
mapper = defaultdict(set)

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
#
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from accounts.models import AccountTemplate, Account
from accounts.tasks import push_accounts_to_assets_task
from assets.models import Asset, Node
from common.serializers.fields import BitChoicesField, ObjectRelatedField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
@@ -19,12 +18,6 @@ class ActionChoicesField(BitChoicesField):
def __init__(self, **kwargs):
super().__init__(choice_cls=ActionChoices, **kwargs)
def to_file_representation(self, value):
return [v['value'] for v in value]
def to_file_internal_value(self, data):
return data
class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
users = ObjectRelatedField(queryset=User.objects, many=True, required=False, label=_('User'))
@@ -38,8 +31,6 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
is_expired = serializers.BooleanField(read_only=True, label=_("Is expired"))
accounts = serializers.ListField(label=_("Account"), required=False)
template_accounts = AccountTemplate.objects.none()
class Meta:
model = AssetPermission
fields_mini = ["id", "name"]
@@ -82,58 +73,8 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
actions.default = list(actions.choices.keys())
@staticmethod
def get_all_assets(nodes, assets):
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
direct_asset_ids = [asset.id for asset in assets]
asset_ids = set(direct_asset_ids + list(node_asset_ids))
return Asset.objects.filter(id__in=asset_ids)
def create_accounts(self, assets):
need_create_accounts = []
account_attribute = [
'name', 'username', 'secret_type', 'secret', 'privileged', 'is_active', 'org_id'
]
for asset in assets:
asset_exist_accounts = Account.objects.none()
for template in self.template_accounts:
asset_exist_accounts |= asset.accounts.filter(
username=template.username,
secret_type=template.secret_type,
)
username_secret_type_dict = asset_exist_accounts.values('username', 'secret_type')
for template in self.template_accounts:
condition = {
'username': template.username,
'secret_type': template.secret_type
}
if condition in username_secret_type_dict:
continue
account_data = {key: getattr(template, key) for key in account_attribute}
account_data['name'] = f"{account_data['name']}-clone"
need_create_accounts.append(Account(**{'asset_id': asset.id, **account_data}))
return Account.objects.bulk_create(need_create_accounts)
def create_and_push_account(self, nodes, assets):
if not self.template_accounts:
return
assets = self.get_all_assets(nodes, assets)
accounts = self.create_accounts(assets)
account_ids = [str(account.id) for account in accounts]
slice_count = 20
for i in range(0, len(account_ids), slice_count):
push_accounts_to_assets_task.delay(account_ids[i:i + slice_count])
def validate_accounts(self, usernames: list[str]):
template_ids = []
account_usernames = []
for username in usernames:
if username.startswith('%'):
template_ids.append(username[1:])
else:
account_usernames.append(username)
self.template_accounts = AccountTemplate.objects.filter(id__in=template_ids)
template_usernames = list(self.template_accounts.values_list('username', flat=True))
return list(set(account_usernames + template_usernames))
def validate_accounts(accounts):
return list(set(accounts))
@classmethod
def setup_eager_loading(cls, queryset):
@@ -171,13 +112,6 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
).distinct()
instance.nodes.add(*nodes_to_set)
def validate(self, attrs):
self.create_and_push_account(
attrs.get("nodes", []),
attrs.get("assets", [])
)
return super().validate(attrs)
def create(self, validated_data):
display = {
"users_display": validated_data.pop("users_display", ""),

View File

@@ -74,9 +74,9 @@ class LDAPSettingSerializer(serializers.Serializer):
)
AUTH_LDAP_CONNECT_TIMEOUT = serializers.IntegerField(
min_value=1, max_value=300,
required=False, label=_('Connect timeout (s)'),
required=False, label=_('Connect timeout'),
)
AUTH_LDAP_SEARCH_PAGED_SIZE = serializers.IntegerField(required=False, label=_('Search paged size (piece)'))
AUTH_LDAP_SEARCH_PAGED_SIZE = serializers.IntegerField(required=False, label=_('Search paged size'))
AUTH_LDAP = serializers.BooleanField(required=False, label=_('Enable LDAP auth'))

View File

@@ -87,7 +87,7 @@ class OIDCSettingSerializer(KeycloakSettingSerializer):
)
AUTH_OPENID_SCOPES = serializers.CharField(required=False, max_length=1024, label=_('Scopes'))
AUTH_OPENID_ID_TOKEN_MAX_AGE = serializers.IntegerField(
required=False, label=_('Id token max age (s)')
required=False, label=_('Id token max age')
)
AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS = serializers.BooleanField(
required=False, label=_('Id token include claims')

View File

@@ -1,9 +1,9 @@
# coding: utf-8
#
#
from celery import shared_task
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.db import transaction
from common.utils import get_logger
from ops.celery.decorator import after_app_ready_start
from ops.celery.utils import (
@@ -22,7 +22,6 @@ def sync_ldap_user():
@shared_task(verbose_name=_('Import ldap user'))
@transaction.atomic
def import_ldap_user():
logger.info("Start import ldap user task")
util_server = LDAPServerUtil()

View File

@@ -18,10 +18,6 @@
margin: 0 auto;
padding: 100px 20px 20px 20px;
}
.ibox-content {
padding: 30px;
}
</style>
{% block custom_head_css_js %} {% endblock %}
</head>
@@ -34,7 +30,7 @@
<h2 class="font-bold">
{% block title %}{% endblock %}
</h2>
<div style="margin: 20px 0 0 0">
<div style="margin: 10px 0">
{% block content %} {% endblock %}
</div>
</div>

View File

@@ -23,7 +23,7 @@
<div class="row">
{% if has_cancel %}
<div class="col-sm-3">
<div class="col-sm-3">
<a href="{{ cancel_url }}" class="btn btn-default block full-width m-b">
{% trans 'Cancel' %}
</a>
@@ -43,7 +43,7 @@
{% endblock %}
{% block custom_foot_js %}
<script>
<script>
var message = ''
var time = '{{ interval }}'
{% if error %}

View File

@@ -3,12 +3,11 @@
- hosts: all
vars:
APPLET_DOWNLOAD_HOST: https://demo.jumpserver.org
IGNORE_VERIFY_CERTS: true
HOST_NAME: test
HOST_ID: 00000000-0000-0000-0000-000000000000
CORE_HOST: https://demo.jumpserver.org
BOOTSTRAP_TOKEN: PleaseChangeMe
RDS_Licensing: false
RDS_Licensing: true
RDS_LicenseServer: 127.0.0.1
RDS_LicensingMode: 4
RDS_fSingleSessionPerUser: 1
@@ -17,6 +16,13 @@
TinkerInstaller: Tinker_Installer.exe
tasks:
- name: Install RDS-Licensing (RDS)
ansible.windows.win_feature:
name: RDS-Licensing
state: present
include_management_tools: yes
when: RDS_Licensing
- name: Install RDS-RD-Server (RDS)
ansible.windows.win_feature:
name: RDS-RD-Server
@@ -38,7 +44,6 @@
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/{{ TinkerInstaller }}"
dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Install JumpServer Tinker (jumpserver)
ansible.windows.win_package:
@@ -59,7 +64,6 @@
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/python-3.10.8-amd64.exe"
dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Install the python-3.10.8
ansible.windows.win_package:
@@ -74,23 +78,11 @@
state: present
register: win_install_python
- name: Check pip command exists
ansible.windows.win_powershell:
script: |
if (Get-Command -Name 'pip' -ErrorAction SilentlyContinue) {
$Ansible.Changed = $false
}
else {
$Ansible.Changed = $true
}
register: check_pip_command
ignore_errors: yes
- name: Reboot if installing requires it
ansible.windows.win_reboot:
post_reboot_delay: 10
test_command: whoami
when: check_pip_command.changed or rds_install.reboot_required or win_install_python.reboot_required
when: rds_install.reboot_required or win_install_python.reboot_required
- name: Set RDS LicenseServer (regedit)
ansible.windows.win_regedit:
@@ -98,7 +90,6 @@
name: LicenseServers
data: "{{ RDS_LicenseServer }}"
type: string
when: RDS_Licensing
- name: Set RDS LicensingMode (regedit)
ansible.windows.win_regedit:
@@ -106,7 +97,6 @@
name: LicensingMode
data: "{{ RDS_LicensingMode }}"
type: dword
when: RDS_Licensing
- name: Set RDS fSingleSessionPerUser (regedit)
ansible.windows.win_regedit:
@@ -114,7 +104,6 @@
name: fSingleSessionPerUser
data: "{{ RDS_fSingleSessionPerUser }}"
type: dword
when: RDS_Licensing
- name: Set RDS MaxDisconnectionTime (regedit)
ansible.windows.win_regedit:
@@ -135,7 +124,6 @@
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/pip_packages.zip"
dest: "{{ ansible_env.TEMP }}\\pip_packages.zip"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Unzip pip_packages
community.windows.win_unzip:
@@ -151,7 +139,6 @@
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chromedriver_win32.zip"
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Unzip chromedriver (Chromium)
community.windows.win_unzip:
@@ -162,7 +149,6 @@
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chrome-win.zip"
dest: "{{ ansible_env.TEMP }}\\chrome-win.zip"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Unzip Chromium (Chromium)
community.windows.win_unzip:
@@ -175,7 +161,7 @@
- 'C:\Program Files\Chrome\chrome-win'
- 'C:\Program Files\JumpServer\drivers\chromedriver_win32'
- name: Set Chromium variables disable Google Api (Chromium)
- name: Set Chromium variables diable Google Api (Chromium)
ansible.windows.win_environment:
level: machine
variables:
@@ -187,7 +173,6 @@
ansible.windows.win_get_url:
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/navicat161_premium_en_x64.exe"
dest: "{{ ansible_env.TEMP }}\\navicat161_premium_en_x64.exe"
validate_certs: "{{ not IGNORE_VERIFY_CERTS }}"
- name: Install navicat (navicat)
ansible.windows.win_package:
@@ -198,8 +183,8 @@
- name: Generate tinkerd component config
ansible.windows.win_shell:
"tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
--token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }} --ignore-verify-certs {{ IGNORE_VERIFY_CERTS }}"
"tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }}
--token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}"
- name: Install tinkerd service
ansible.windows.win_shell:
@@ -222,3 +207,4 @@
- name: Sync all remote applets
ansible.windows.win_shell:
"tinkerd install all"

View File

@@ -29,18 +29,8 @@ class DeployOptionsSerializer(serializers.Serializer):
(0, _("Enabled")),
)
CORE_HOST = serializers.CharField(
default=settings.SITE_URL, label=_('Core API'), max_length=1024,
help_text=_("""
Tips: The application release machine communicates with the Core service.
If the release machine and the Core service are on the same network segment,
it is recommended to fill in the intranet address, otherwise fill in the current site URL
<br>
eg: https://172.16.10.110 or https://dev.jumpserver.com
""")
)
IGNORE_VERIFY_CERTS = serializers.BooleanField(default=True, label=_("Ignore Certificate Verification"))
RDS_Licensing = serializers.BooleanField(default=False, label=_("Existing RDS license"))
CORE_HOST = serializers.CharField(default=settings.SITE_URL, label=_('API Server'), max_length=1024)
RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing"))
RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024)
RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=2, label=_('RDS Licensing Mode'))
RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1,

View File

@@ -3,7 +3,6 @@ from rest_framework import serializers
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
from common.serializers import BulkModelSerializer
from common.serializers.fields import ObjectRelatedField
from ..models import Endpoint, EndpointRule
from ..utils import db_port_manager
@@ -33,7 +32,7 @@ class EndpointSerializer(BulkModelSerializer):
'comment', 'date_created', 'date_updated', 'created_by'
]
extra_kwargs = {
'host': {'help_text': _('Visit IP/Host, if empty, use the current request instead')},
'host': {'help_text': 'Visit IP/host, if empty, use the current request instead'},
}
def get_oracle_port(self, obj: Endpoint):
@@ -62,15 +61,13 @@ class EndpointRuleSerializer(BulkModelSerializer):
default=['*'], label=_('IP'), help_text=_ip_group_help_text,
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator])
)
endpoint = ObjectRelatedField(
allow_null=True, required=False, queryset=Endpoint.objects, label=_('Endpoint')
)
endpoint_display = serializers.ReadOnlyField(source='endpoint.name', label=_('Endpoint'))
class Meta:
model = EndpointRule
fields_mini = ['id', 'name']
fields_small = fields_mini + ['ip_group', 'priority']
fields_fk = ['endpoint']
fields_fk = ['endpoint', 'endpoint_display']
fields = fields_mini + fields_small + fields_fk + [
'comment', 'date_created', 'date_updated', 'created_by'
]

View File

@@ -1,16 +1,12 @@
# -*- coding: utf-8 -*-
#
import phonenumbers
from functools import partial
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.serializers import CommonBulkSerializerMixin
from common.serializers.fields import (
EncryptedField, ObjectRelatedField, LabeledChoiceField, PhoneField
)
from common.serializers.fields import EncryptedField, ObjectRelatedField, LabeledChoiceField
from common.utils import pretty_string, get_logger
from common.validators import PhoneValidator
from rbac.builtin import BuiltinRole
@@ -85,8 +81,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
password_strategy = LabeledChoiceField(
choices=PasswordStrategy.choices,
default=PasswordStrategy.email,
allow_null=True,
required=False,
write_only=True,
label=_("Password strategy"),
)
mfa_enabled = serializers.BooleanField(read_only=True, label=_("MFA enabled"))
@@ -105,9 +101,6 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
label=_("Password"), required=False, allow_blank=True,
allow_null=True, max_length=1024,
)
phone = PhoneField(
validators=[PhoneValidator()], required=False, allow_blank=True, allow_null=True, label=_("Phone")
)
custom_m2m_fields = {
"system_roles": [BuiltinRole.system_user],
"org_roles": [BuiltinRole.org_user],
@@ -149,7 +142,6 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
# 在serializer 上定义的字段
fields_custom = ["login_blocked", "password_strategy"]
fields = fields_verbose + fields_fk + fields_m2m + fields_custom
fields_unexport = ["avatar_url", ]
read_only_fields = [
"date_joined", "last_login", "created_by",
@@ -174,7 +166,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
"created_by": {"read_only": True, "allow_blank": True},
"role": {"default": "User"},
"is_otp_secret_key_bound": {"label": _("Is OTP bound")},
'mfa_level': {'label': _("MFA level")},
"phone": {"validators": [PhoneValidator()]},
}
def validate_password(self, password):

View File

@@ -58,7 +58,7 @@
{% if not b.can_disable %} disabled {% endif %}
onclick="goTo('{{ b.get_disable_url }}')"
>
{% trans 'Reset' %}
{% trans 'Disable' %}
</button>
<span class="help-inline">{{ b.help_text_of_disable }}</span>
{% else %}

View File

@@ -29,7 +29,6 @@ pycparser==2.21
cryptography==38.0.4
pycryptodome==3.15.0
pycryptodomex==3.15.0
phonenumbers==8.13.8
gmssl==3.2.1
itsdangerous==1.1.0
pyotp==2.6.0