Merge branch 'dev' of github.com:jumpserver/jumpserver into pr@dev@feat_account_backend_support_aws

pull/14515/head
jiangweidong 2 days ago
commit a6fa1dab2a

@ -24,6 +24,7 @@ ENV LANG=en_US.UTF-8 \
PATH=/opt/py3/bin:$PATH
ARG DEPENDENCIES=" \
libldap2-dev \
libx11-dev"
ARG TOOLS=" \

@ -30,6 +30,6 @@
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
gateway_args: "{{ jms_gateway | default(None) }}"
gateway_args: "{{ jms_gateway | default({}) }}"
when: account.secret_type == "password"
delegate_to: localhost

@ -30,6 +30,6 @@
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
gateway_args: "{{ jms_gateway | default(None) }}"
gateway_args: "{{ jms_gateway | default({}) }}"
when: account.secret_type == "password"
delegate_to: localhost

@ -1,18 +1,21 @@
from importlib import import_module
from django.utils.functional import LazyObject
from django.utils.functional import LazyObject, empty
from common.utils import get_logger
from ..const import VaultTypeChoices
__all__ = ['vault_client', 'get_vault_client']
__all__ = ['vault_client', 'get_vault_client', 'refresh_vault_client']
logger = get_logger(__file__)
def get_vault_client(raise_exception=False, **kwargs):
tp = kwargs.get('VAULT_BACKEND') if kwargs.get('VAULT_ENABLED') else VaultTypeChoices.local
# TODO: Temporary processing, subsequent deletion
tp = VaultTypeChoices.local if tp == VaultTypeChoices.azure else tp
try:
module_path = f'apps.accounts.backends.{tp}.main'
client = import_module(module_path).Vault(**kwargs)
@ -38,3 +41,7 @@ class VaultClient(LazyObject):
""" 为了安全, 页面修改配置, 重启服务后才会重新初始化 vault_client """
vault_client = VaultClient()
def refresh_vault_client():
vault_client._wrapped = empty

@ -49,9 +49,9 @@ class SecretStrategy(models.TextChoices):
class SSHKeyStrategy(models.TextChoices):
add = 'add', _('Append SSH KEY')
set = 'set', _('Empty and append SSH KEY')
set_jms = 'set_jms', _('Replace (Replace only keys pushed by JumpServer) ')
set = 'set', _('Empty and append SSH KEY')
add = 'add', _('Append SSH KEY')
class TriggerChoice(models.TextChoices, TreeChoices):

@ -50,7 +50,7 @@ class Migration(migrations.Migration):
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy')),
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy')),
('ssh_key_change_strategy', models.CharField(choices=[('set_jms', 'Replace (Replace only keys pushed by JumpServer) '), ('set', 'Empty and append SSH KEY'), ('add', 'Append SSH KEY')], default='set_jms', max_length=16, verbose_name='SSH key change strategy')),
],
options={
'verbose_name': 'Change secret automation',
@ -76,7 +76,7 @@ class Migration(migrations.Migration):
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy')),
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy')),
('ssh_key_change_strategy', models.CharField(choices=[('set_jms', 'Replace (Replace only keys pushed by JumpServer) '), ('set', 'Empty and append SSH KEY'), ('add', 'Append SSH KEY')], default='set_jms', max_length=16, verbose_name='SSH key change strategy')),
('triggers', models.JSONField(default=list, max_length=16, verbose_name='Triggers')),
('username', models.CharField(max_length=128, verbose_name='Username')),
('action', models.CharField(max_length=16, verbose_name='Action')),

@ -51,7 +51,7 @@ class AutomationExecution(AssetAutomationExecution):
class ChangeSecretMixin(SecretWithRandomMixin):
ssh_key_change_strategy = models.CharField(
choices=SSHKeyStrategy.choices, max_length=16,
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
default=SSHKeyStrategy.set_jms, verbose_name=_('SSH key change strategy')
)
get_all_assets: callable # get all assets

@ -3,14 +3,17 @@ from collections import defaultdict
from django.db.models.signals import post_delete
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from django.utils.functional import LazyObject
from django.utils.translation import gettext_noop
from accounts.backends import vault_client
from accounts.backends import vault_client, refresh_vault_client
from accounts.const import Source
from audits.const import ActivityChoices
from audits.signal_handlers import create_activities
from common.decorators import merge_delay_run
from common.signals import django_ready
from common.utils import get_logger, i18n_fmt
from common.utils.connection import RedisPubSub
from .models import Account, AccountTemplate
from .tasks.push_account import push_accounts_to_assets_task
@ -91,3 +94,18 @@ class VaultSignalHandler(object):
for model in (Account, AccountTemplate, Account.history.model):
post_save.connect(VaultSignalHandler.save_to_vault, sender=model)
post_delete.connect(VaultSignalHandler.delete_to_vault, sender=model)
class VaultPubSub(LazyObject):
def _setup(self):
self._wrapped = RedisPubSub('refresh_vault')
vault_pub_sub = VaultPubSub()
@receiver(django_ready)
def subscribe_vault_change(sender, **kwargs):
logger.debug("Start subscribe vault change")
vault_pub_sub.subscribe(lambda name: refresh_vault_client())

@ -7,6 +7,7 @@ from django.db.models import F, Value, CharField, Q
from django.db.models.functions import Cast
from django.http import HttpResponse, FileResponse
from django.utils.encoding import escape_uri_path
from django_celery_beat.models import PeriodicTask
from rest_framework import generics
from rest_framework import status
from rest_framework import viewsets
@ -74,6 +75,21 @@ class JobsAuditViewSet(OrgModelViewSet):
queryset = queryset.exclude(type=Types.upload_file).filter(instant=False)
return queryset
def perform_update(self, serializer):
job = self.get_object()
is_periodic = serializer.validated_data.get('is_periodic')
if job.is_periodic != is_periodic:
job.is_periodic = is_periodic
job.save()
name, task, args, kwargs = job.get_register_task()
task_obj = PeriodicTask.objects.filter(name=name).first()
if task_obj:
is_periodic = job.is_periodic
if task_obj.enabled != is_periodic:
task_obj.enabled = is_periodic
task_obj.save()
return super().perform_update(serializer)
class FTPLogViewSet(OrgModelViewSet):
model = FTPLog

@ -38,10 +38,12 @@ class JobsAuditSerializer(JobSerializer):
material = serializers.ReadOnlyField(label=_("Command"))
summary = serializers.ReadOnlyField(label=_("Summary"))
crontab = serializers.ReadOnlyField(label=_("Execution cycle"))
is_periodic_display = serializers.BooleanField(read_only=True, source='is_periodic')
class Meta(JobSerializer.Meta):
read_only_fields = [
"id", 'name', 'args', 'material', 'type', 'crontab', 'interval', 'date_last_run', 'summary', 'created_by'
"id", 'name', 'args', 'material', 'type', 'crontab', 'interval', 'date_last_run', 'summary', 'created_by',
'is_periodic_display'
]
fields = read_only_fields + ['is_periodic']

@ -5,6 +5,7 @@ from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _
from authentication.mixins import MFAFaceMixin
from common.const import LicenseEditionChoices
from settings.api import settings
@ -32,7 +33,10 @@ class MFAFace(BaseMFA, MFAFaceMixin):
@staticmethod
def global_enabled():
return settings.XPACK_LICENSE_IS_VALID and settings.FACE_RECOGNITION_ENABLED
return settings.XPACK_LICENSE_IS_VALID \
and LicenseEditionChoices.ULTIMATE == \
LicenseEditionChoices.from_key(settings.XPACK_LICENSE_EDITION) \
and settings.FACE_RECOGNITION_ENABLED
def get_enable_url(self) -> str:
return reverse('authentication:user-face-enable')

@ -35,7 +35,7 @@ class MFAMiddleware:
# 这个是 mfa 登录页需要的请求, 也得放出来, 用户其实已经在 CAS/OIDC 中完成登录了
white_urls = [
'login/mfa', 'mfa/select', 'jsi18n/', '/static/',
'login/mfa', 'mfa/select', 'mfa/face','jsi18n/', '/static/',
'/profile/otp', '/logout/',
]
for url in white_urls:

@ -238,7 +238,7 @@ class MFAFaceMixin:
if not self.is_context_success(context):
msg = context.get('error_message', '')
raise RuntimeError("Face recognition failed,{}".format(msg))
raise RuntimeError(msg)
face_code = context.get('face_code')
if not face_code:

@ -2,28 +2,18 @@
{% load i18n %}
{% load static %}
{% block content %}
<style>
.ibox-content {
padding: 0;
}
h2 {
display: none;
}
.ibox-context-margin {
margin: 0;
}
</style>
{% if 'code' in form.errors %}
<p class="red-fonts">{{ form.code.errors.as_text }}</p>
<div class="alert alert-danger" id="messages">
<p class="red-fonts">{{ form.code.errors.as_text }}</p>
</div>
{% endif %}
<div id="retry_container" style="text-align: center; margin-top: 20px; display: none;">
<button id="retry_button" class="btn btn-primary">{% trans 'Retry' %}</button>
</div>
<form class="m-t" role="form" method="post" action="" style="display: none">
{% csrf_token %}
<button id="submit_button" type="submit" style="display: none"></button>
@ -40,9 +30,7 @@
</iframe>
</div>
<div id="retry_container" style="text-align: center; margin-top: 20px; display: none;">
<button id="retry_button" class="btn btn-primary">{% trans 'Retry' %}</button>
</div>
<script>
$(document).ready(function () {

@ -76,3 +76,32 @@ class Language(models.TextChoices):
COUNTRY_CALLING_CODES = get_country_phone_choices()
class LicenseEditionChoices(models.TextChoices):
COMMUNITY = 'community', _('Community edition')
BASIC = 'basic', _('Basic edition')
STANDARD = 'standard', _('Standard edition')
PROFESSIONAL = 'professional', _('Professional edition')
ULTIMATE = 'ultimate', _('Ultimate edition')
@staticmethod
def from_key(key: str):
for choice in LicenseEditionChoices:
if choice == key:
return choice
return LicenseEditionChoices.COMMUNITY
@staticmethod
def parse_license_edition(info):
count = info.get('license', {}).get('count', 0)
if 50 >= count > 0:
return LicenseEditionChoices.BASIC
elif count <= 500:
return LicenseEditionChoices.STANDARD
elif count < 5000:
return LicenseEditionChoices.PROFESSIONAL
elif count >= 5000:
return LicenseEditionChoices.ULTIMATE
else:
return LicenseEditionChoices.COMMUNITY

@ -73,6 +73,7 @@ known_unauth_urls = [
"/api/v1/authentication/password/reset-code/",
"/api/v1/authentication/login-confirm-ticket/status/",
"/api/v1/authentication/mfa/select/",
"/api/v1/authentication/mfa/face/context/",
"/api/v1/authentication/mfa/send-code/",
"/api/v1/authentication/sso/login/",
"/api/v1/authentication/user-session/",

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-19 18:55+0800\n"
"POT-Creation-Date: 2024-11-20 19:32+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"
@ -35,7 +35,7 @@ msgstr "生成资产或应用相关备份信息文件"
#: accounts/automations/backup_account/handlers.py:156
#: accounts/automations/backup_account/handlers.py:295
#: accounts/automations/backup_account/manager.py:40 ops/serializers/job.py:82
#: accounts/automations/backup_account/manager.py:40 ops/serializers/job.py:94
#: settings/templates/ldap/_msg_import_ldap_user.html:7
msgid "Time cost"
msgstr "花费时间"
@ -57,7 +57,7 @@ msgid "The backup file will be sent to"
msgstr "备份文件将被发送至"
#: accounts/automations/backup_account/handlers.py:213
#: users/forms/profile.py:75
#: users/forms/profile.py:76
msgid "Finish"
msgstr "完成"
@ -130,7 +130,7 @@ msgstr ">>> 开始执行测试网关账号可连接性任务"
#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:52
#: settings/serializers/auth/ldap_ha.py:34 settings/serializers/msg.py:37
#: settings/serializers/terminal.py:28 terminal/serializers/storage.py:123
#: terminal/serializers/storage.py:142 users/forms/profile.py:21
#: terminal/serializers/storage.py:142 users/forms/profile.py:22
#: users/serializers/user.py:144
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18
@ -149,7 +149,7 @@ msgid "Access key"
msgstr "Access key"
#: accounts/const/account.py:9 authentication/backends/passkey/models.py:16
#: authentication/models/sso_token.py:14 settings/serializers/feature.py:74
#: authentication/models/sso_token.py:14 settings/serializers/feature.py:83
msgid "Token"
msgstr "令牌"
@ -233,17 +233,17 @@ msgstr "指定"
msgid "Random generate"
msgstr "随机生成"
#: accounts/const/automation.py:52 ops/const.py:13
msgid "Append SSH KEY"
msgstr "追加"
#: accounts/const/automation.py:52 ops/const.py:15
msgid "Replace (Replace only keys pushed by JumpServer) "
msgstr "替换 (只替换由 JumpServer 推送的密钥)"
#: accounts/const/automation.py:53 ops/const.py:14
msgid "Empty and append SSH KEY"
msgstr "清空所有并添加"
#: accounts/const/automation.py:54 ops/const.py:15
msgid "Replace (Replace only keys pushed by JumpServer) "
msgstr "替换 (只替换由 JumpServer 推送的密钥)"
#: accounts/const/automation.py:54 ops/const.py:13
msgid "Append SSH KEY"
msgstr "追加"
#: accounts/const/automation.py:59
msgid "On asset create"
@ -297,8 +297,8 @@ msgstr "仅创建"
#: authentication/serializers/password_mfa.py:17
#: authentication/serializers/password_mfa.py:25
#: notifications/backends/__init__.py:10 settings/serializers/msg.py:21
#: settings/serializers/msg.py:61 users/forms/profile.py:100
#: users/forms/profile.py:108 users/models/user/__init__.py:65
#: settings/serializers/msg.py:61 users/forms/profile.py:101
#: users/forms/profile.py:111 users/models/user/__init__.py:65
#: users/templates/users/forgot_password.html:162
#: users/views/profile/reset.py:94
msgid "Email"
@ -319,11 +319,11 @@ msgstr "待定的"
msgid "Database"
msgstr "数据库"
#: accounts/const/vault.py:9 settings/serializers/feature.py:69
#: accounts/const/vault.py:9 settings/serializers/feature.py:78
msgid "HCP Vault"
msgstr "HashiCorp Vault"
#: accounts/const/vault.py:10 settings/serializers/feature.py:82
#: accounts/const/vault.py:10 settings/serializers/feature.py:91
msgid "Azure Key Vault"
msgstr "Azure Key Vault"
@ -510,7 +510,7 @@ msgstr "原因"
#: accounts/models/automations/backup_account.py:136
#: accounts/serializers/automations/change_secret.py:117
#: accounts/serializers/automations/change_secret.py:152
#: ops/serializers/job.py:80 terminal/serializers/session.py:54
#: ops/serializers/job.py:92 terminal/serializers/session.py:54
msgid "Is success"
msgstr "是否成功"
@ -593,7 +593,7 @@ msgstr "结束日期"
#: accounts/models/automations/change_secret.py:44
#: assets/models/automations/base.py:113
#: assets/serializers/automations/base.py:39 audits/models.py:208
#: audits/serializers.py:76 ops/models/base.py:49 ops/models/job.py:235
#: audits/serializers.py:78 ops/models/base.py:49 ops/models/job.py:235
#: terminal/models/applet/applet.py:331 terminal/models/applet/host.py:140
#: terminal/models/component/status.py:30
#: terminal/models/virtualapp/virtualapp.py:99
@ -635,8 +635,8 @@ msgstr "最后登录日期"
#: authentication/forms.py:23 authentication/models/temp_token.py:9
#: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9
#: terminal/serializers/storage.py:136 users/forms/profile.py:31
#: users/forms/profile.py:114 users/models/user/__init__.py:63
#: terminal/serializers/storage.py:136 users/forms/profile.py:32
#: users/forms/profile.py:117 users/models/user/__init__.py:63
#: users/templates/users/_msg_user_created.html:12
#: xpack/plugins/cloud/serializers/account_attrs.py:26
msgid "Username"
@ -665,7 +665,7 @@ msgstr "触发方式"
#: accounts/models/automations/push_account.py:16 acls/models/base.py:41
#: acls/serializers/base.py:57 assets/models/cmd_filter.py:81
#: audits/models.py:92 audits/serializers.py:106
#: audits/models.py:92 audits/serializers.py:108
#: authentication/serializers/connect_token_secret.py:119
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/serializers/permission.py:52 perms/serializers/permission.py:74
@ -734,7 +734,7 @@ msgstr "密码规则"
#: terminal/models/component/terminal.py:85
#: terminal/models/virtualapp/provider.py:10
#: terminal/models/virtualapp/virtualapp.py:19 tickets/api/ticket.py:87
#: users/forms/profile.py:32 users/models/group.py:13
#: users/forms/profile.py:33 users/models/group.py:13
#: users/models/preference.py:11 users/models/user/__init__.py:64
#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:310
#: xpack/plugins/cloud/serializers/task.py:75
@ -885,8 +885,8 @@ msgstr "类别"
#: acls/serializers/command_acl.py:19 assets/models/automations/base.py:20
#: assets/models/cmd_filter.py:74 assets/models/platform.py:96
#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:159
#: assets/serializers/platform.py:171 audits/serializers.py:75
#: audits/serializers.py:192
#: assets/serializers/platform.py:171 audits/serializers.py:77
#: audits/serializers.py:194
#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:153
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
#: terminal/models/component/storage.py:58
@ -959,7 +959,7 @@ msgstr "ID"
#: acls/templates/acls/user_login_reminder.html:8
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:54
#: audits/models.py:90 audits/models.py:172 audits/models.py:271
#: audits/serializers.py:193 authentication/models/connection_token.py:32
#: audits/serializers.py:195 authentication/models/connection_token.py:32
#: authentication/models/ssh_key.py:22 authentication/models/sso_token.py:16
#: notifications/models/notification.py:12
#: perms/api/user_permission/mixin.py:55 perms/models/asset_permission.py:63
@ -1136,17 +1136,17 @@ msgstr "参数设置,目前只对 AIX LINUX UNIX 类型的资产有效。"
msgid "Automation task execution"
msgstr "自动化任务执行历史"
#: accounts/signal_handlers.py:48
#: accounts/signal_handlers.py:51
#, python-format
msgid "Push related accounts to assets: %s, by system"
msgstr "推送账号到资产: %s, 由系统执行"
#: accounts/signal_handlers.py:57
#: accounts/signal_handlers.py:60
#, python-format
msgid "Add account: %s"
msgstr "添加账号: %s"
#: accounts/signal_handlers.py:59
#: accounts/signal_handlers.py:62
#, python-format
msgid "Delete account: %s"
msgstr "删除账号: %s"
@ -1269,11 +1269,11 @@ msgid ""
" Accounts' this task will be executed"
msgstr "当在控制台-账号模板-账号-同步更新账号信息点击同步时,执行该任务"
#: accounts/tasks/vault.py:32
#: accounts/tasks/vault.py:33
msgid "Sync secret to vault"
msgstr "同步密文到 vault"
#: accounts/tasks/vault.py:34
#: accounts/tasks/vault.py:35
msgid ""
"When clicking 'Sync' in 'System Settings - Features - Account Storage' this "
"task will be executed"
@ -1388,7 +1388,7 @@ msgstr "激活中"
#: acls/models/base.py:81 perms/serializers/permission.py:42
#: tickets/models/flow.py:23 users/models/preference.py:16
#: users/serializers/group.py:21 users/serializers/user.py:424
#: users/serializers/group.py:21 users/serializers/user.py:432
msgid "Users"
msgstr "用户"
@ -1400,7 +1400,7 @@ msgid "Accounts"
msgstr "账号"
#: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60
#: audits/serializers.py:38 ops/serializers/job.py:79 terminal/const.py:88
#: audits/serializers.py:38 ops/serializers/job.py:91 terminal/const.py:88
#: terminal/models/session/session.py:43 terminal/serializers/command.py:18
#: terminal/templates/terminal/_msg_command_alert.html:12
#: terminal/templates/terminal/_msg_command_execute_alert.html:10
@ -1588,7 +1588,7 @@ msgid "Login city"
msgstr "登录城市"
#: acls/templates/acls/user_login_reminder.html:11 audits/models.py:197
#: audits/models.py:266 audits/serializers.py:90
#: audits/models.py:266 audits/serializers.py:92
msgid "User agent"
msgstr "用户代理"
@ -1722,7 +1722,7 @@ msgstr "脚本"
#: assets/const/category.py:10 assets/models/asset/host.py:8
#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:76
#: settings/serializers/feature.py:71 settings/serializers/feature.py:84
#: settings/serializers/feature.py:80 settings/serializers/feature.py:93
#: settings/serializers/msg.py:30 terminal/models/component/endpoint.py:14
#: terminal/serializers/applet.py:17 xpack/plugins/cloud/manager.py:89
#: xpack/plugins/cloud/serializers/account_attrs.py:72
@ -2031,7 +2031,7 @@ msgstr "忽略证书校验"
msgid "Postgresql SSL mode"
msgstr "PostgreSQL SSL 模式"
#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:114
#: assets/models/asset/gpt.py:8 settings/serializers/feature.py:123
msgid "Proxy"
msgstr "代理"
@ -2858,7 +2858,7 @@ msgstr "是"
msgid "No"
msgstr "否"
#: audits/models.py:47 rbac/tree.py:67
#: audits/models.py:47
msgid "Job audit log"
msgstr "作业审计日志"
@ -2867,7 +2867,7 @@ msgstr "作业审计日志"
msgid "Remote addr"
msgstr "远端地址"
#: audits/models.py:61 audits/serializers.py:60
#: audits/models.py:61 audits/serializers.py:62
msgid "Operate"
msgstr "操作"
@ -2892,12 +2892,12 @@ msgstr "会话"
msgid "File transfer log"
msgstr "文件传输"
#: audits/models.py:94 audits/serializers.py:108
#: audits/models.py:94 audits/serializers.py:110
msgid "Resource Type"
msgstr "资源类型"
#: audits/models.py:95 audits/models.py:98 audits/models.py:144
#: audits/serializers.py:107 labels/serializers.py:46
#: audits/serializers.py:109 labels/serializers.py:46
msgid "Resource"
msgstr "资源"
@ -2939,9 +2939,9 @@ msgstr "登录方式"
msgid "Login IP"
msgstr "登录 IP"
#: audits/models.py:200 audits/serializers.py:74
#: audits/models.py:200 audits/serializers.py:76
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: users/forms/profile.py:63 users/models/user/__init__.py:86
#: users/forms/profile.py:64 users/models/user/__init__.py:86
#: users/serializers/profile.py:70
msgid "MFA"
msgstr "MFA"
@ -2992,20 +2992,20 @@ msgstr "汇总"
msgid "Execution cycle"
msgstr "周期执行"
#: audits/serializers.py:91
#: audits/serializers.py:93
msgid "Reason display"
msgstr "原因描述"
#: audits/serializers.py:92 audits/serializers.py:206
#: audits/serializers.py:94 audits/serializers.py:208
msgid "Auth backend display"
msgstr "认证方式"
#: audits/serializers.py:156
#: audits/serializers.py:158
#, python-format
msgid "%s %s this resource"
msgstr "用户 %s %s 了当前资源"
#: audits/serializers.py:194 authentication/models/connection_token.py:47
#: audits/serializers.py:196 authentication/models/connection_token.py:47
#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:80
#: tickets/models/ticket/apply_application.py:31
#: tickets/models/ticket/apply_asset.py:20 users/models/user/__init__.py:105
@ -3150,22 +3150,22 @@ msgstr "ACL 动作是复核"
msgid "Current user not support mfa type: {}"
msgstr "当前用户不支持 MFA 类型: {}"
#: authentication/api/password.py:33 terminal/api/session/session.py:334
#: authentication/api/password.py:34 terminal/api/session/session.py:334
#: users/views/profile/reset.py:63
msgid "User does not exist: {}"
msgstr "用户不存在: {}"
#: authentication/api/password.py:33 users/views/profile/reset.py:166
#: authentication/api/password.py:34 users/views/profile/reset.py:166
msgid "No user matched"
msgstr "没有匹配到用户"
#: authentication/api/password.py:37
#: authentication/api/password.py:38
msgid ""
"The user is from {}, please go to the corresponding system to change the "
"password"
msgstr "用户来自 {} 请去相应系统修改密码"
#: authentication/api/password.py:65
#: authentication/api/password.py:69
#: authentication/templates/authentication/login.html:393
#: users/templates/users/forgot_password.html:41
#: users/templates/users/forgot_password.html:42
@ -3404,15 +3404,15 @@ msgstr "您的密码无效"
msgid "Please wait for %s seconds before retry"
msgstr "请在 %s 秒后重试"
#: authentication/errors/redirect.py:85 authentication/mixins.py:365
#: authentication/errors/redirect.py:85 authentication/mixins.py:373
msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors/redirect.py:93 authentication/mixins.py:374
#: authentication/errors/redirect.py:93 authentication/mixins.py:382
msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码"
#: authentication/errors/redirect.py:101 authentication/mixins.py:383
#: authentication/errors/redirect.py:101 authentication/mixins.py:391
msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录"
@ -3433,7 +3433,7 @@ msgstr "MFA 类型"
msgid "Captcha"
msgstr "验证码"
#: authentication/forms.py:66 users/forms/profile.py:27
#: authentication/forms.py:66 users/forms/profile.py:28
msgid "MFA code"
msgstr "MFA 验证码"
@ -3497,28 +3497,28 @@ msgstr "Radius 动态安全码"
msgid "Radius global enabled, cannot disable"
msgstr "Radius MFA 全局开启,无法被禁用"
#: authentication/mfa/sms.py:7
#: authentication/mfa/sms.py:8
msgid "SMS verify code invalid"
msgstr "短信验证码校验失败"
#: authentication/mfa/sms.py:12 authentication/serializers/password_mfa.py:17
#: authentication/mfa/sms.py:13 authentication/serializers/password_mfa.py:17
#: authentication/serializers/password_mfa.py:25
#: settings/serializers/auth/sms.py:18 settings/serializers/auth/sms.py:36
#: users/forms/profile.py:103 users/forms/profile.py:108
#: users/forms/profile.py:104 users/forms/profile.py:111
#: users/templates/users/forgot_password.html:157
#: users/views/profile/reset.py:100
msgid "SMS"
msgstr "短信"
#: authentication/mfa/sms.py:13
#: authentication/mfa/sms.py:14
msgid "SMS verification code"
msgstr "短信验证码"
#: authentication/mfa/sms.py:57
#: authentication/mfa/sms.py:63
msgid "Set phone number to enable"
msgstr "设置手机号码启用"
#: authentication/mfa/sms.py:61
#: authentication/mfa/sms.py:67
msgid "Clear phone number to disable"
msgstr "清空手机号码禁用"
@ -3536,11 +3536,11 @@ msgid ""
" The current user source is {}. Please contact the administrator."
msgstr "管理员已开启'仅允许从用户来源登录',当前用户来源为{},请联系管理员。"
#: authentication/mixins.py:311
#: authentication/mixins.py:319
msgid "The MFA type ({}) is not enabled"
msgstr "该 MFA ({}) 方式没有启用"
#: authentication/mixins.py:353
#: authentication/mixins.py:361
msgid "Please change your password"
msgstr "请修改密码"
@ -3641,7 +3641,7 @@ msgid "Private key"
msgstr "ssh私钥"
#: authentication/models/ssh_key.py:18 settings/serializers/terminal.py:34
#: users/forms/profile.py:172 users/models/user/__init__.py:96
#: users/forms/profile.py:175 users/models/user/__init__.py:96
#: xpack/plugins/cloud/serializers/account_attrs.py:210
msgid "Public key"
msgstr "SSH公钥"
@ -3752,7 +3752,7 @@ msgid ""
"downloaded once"
msgstr "创建完成后请下载私钥,每个私钥只有一次下载机会"
#: authentication/serializers/ssh_key.py:57 users/forms/profile.py:161
#: authentication/serializers/ssh_key.py:57 users/forms/profile.py:164
#: users/serializers/profile.py:133 users/serializers/profile.py:160
msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法"
@ -3886,7 +3886,7 @@ msgstr "重新申请"
#: authentication/templates/authentication/_msg_reset_password_code.html:12
#: terminal/models/session/sharing.py:27 terminal/models/session/sharing.py:97
#: terminal/templates/terminal/_msg_session_sharing.html:12
#: users/forms/profile.py:106 users/templates/users/forgot_password.html:98
#: users/forms/profile.py:108 users/templates/users/forgot_password.html:98
msgid "Verify code"
msgstr "验证码"
@ -3934,7 +3934,7 @@ msgstr "如果这次公钥更新不是由你发起的,那么你的账号可能
msgid "Cancel"
msgstr "取消"
#: authentication/templates/authentication/face_capture.html:44
#: authentication/templates/authentication/face_capture.html:14
msgid "Retry"
msgstr "重试"
@ -4706,6 +4706,10 @@ msgstr "Ansible 已禁用"
msgid "Skip hosts below:"
msgstr "跳过以下主机: "
#: ops/api/adhoc.py:32
msgid "Deleting other people's script is not allowed"
msgstr "不允许删除别人的脚本"
#: ops/api/celery.py:66 ops/api/celery.py:81
msgid "Waiting task start"
msgstr "等待任务开始"
@ -4750,6 +4754,10 @@ msgid ""
"The task is being created and cannot be interrupted. Please try again later."
msgstr "正在创建任务,无法中断,请稍后重试。"
#: ops/api/playbook.py:49
msgid "Deleting other people's playbook is not allowed"
msgstr "不允许删除别人的playbook"
#: ops/api/playbook.py:55
msgid "Currently playbook is being used in a job"
msgstr "当前 playbook 正在作业中使用"
@ -4815,7 +4823,7 @@ msgid "VCS"
msgstr "VCS"
#: ops/const.py:38 ops/models/adhoc.py:44 ops/models/variable.py:26
#: settings/serializers/feature.py:145
#: settings/serializers/feature.py:154
msgid "Adhoc"
msgstr "命令"
@ -5024,7 +5032,7 @@ msgstr "运行用户"
msgid "Run as policy"
msgstr "用户策略"
#: ops/models/job.py:223 ops/models/variable.py:28 ops/serializers/job.py:98
#: ops/models/job.py:223 ops/models/variable.py:28 ops/serializers/job.py:110
#: terminal/notifications.py:182
msgid "Job"
msgstr "作业"
@ -5115,23 +5123,23 @@ msgstr "下次执行时间"
msgid "Execute after saving"
msgstr "保存后执行"
#: ops/serializers/job.py:58 terminal/serializers/session.py:49
#: ops/serializers/job.py:70 terminal/serializers/session.py:49
msgid "Duration"
msgstr "时长"
#: ops/serializers/job.py:78
#: ops/serializers/job.py:90
msgid "Job type"
msgstr "任务类型"
#: ops/serializers/job.py:81 terminal/serializers/session.py:58
#: ops/serializers/job.py:93 terminal/serializers/session.py:58
msgid "Is finished"
msgstr "是否完成"
#: ops/serializers/job.py:95
#: ops/serializers/job.py:107
msgid "Task id"
msgstr "任务 ID"
#: ops/serializers/job.py:104
#: ops/serializers/job.py:116
msgid "You do not have permission for the current job."
msgstr "你没有当前作业的权限。"
@ -5441,7 +5449,7 @@ msgid "today"
msgstr "今天"
#: perms/notifications.py:12 perms/notifications.py:44
#: settings/serializers/feature.py:136
#: settings/serializers/feature.py:145
msgid "day"
msgstr "天"
@ -5696,7 +5704,7 @@ msgstr "账号改密"
msgid "App ops"
msgstr "作业中心"
#: rbac/tree.py:57 settings/serializers/feature.py:142
#: rbac/tree.py:57 settings/serializers/feature.py:151
msgid "Feature"
msgstr "功能"
@ -5719,10 +5727,14 @@ msgid "Appearance"
msgstr "界面"
#: rbac/tree.py:65 xpack/plugins/license/meta.py:10
#: xpack/plugins/license/models.py:155
#: xpack/plugins/license/models.py:154
msgid "License"
msgstr "许可证"
#: rbac/tree.py:67
msgid "Job audit"
msgstr "作业审计"
#: rbac/tree.py:159
msgid "App organizations"
msgstr "组织管理"
@ -5731,8 +5743,8 @@ msgstr "组织管理"
msgid "Ticket comment"
msgstr "工单评论"
#: rbac/tree.py:161 settings/serializers/feature.py:123
#: settings/serializers/feature.py:125 tickets/models/ticket/general.py:308
#: rbac/tree.py:161 settings/serializers/feature.py:132
#: settings/serializers/feature.py:134 tickets/models/ticket/general.py:308
msgid "Ticket"
msgstr "工单"
@ -6143,13 +6155,13 @@ msgstr "图标"
msgid "Service provider"
msgstr "服务提供商"
#: settings/serializers/auth/oauth2.py:31 settings/serializers/feature.py:87
#: settings/serializers/auth/oauth2.py:31 settings/serializers/feature.py:96
#: xpack/plugins/cloud/serializers/account_attrs.py:35
msgid "Client ID"
msgstr "客户端 ID"
#: settings/serializers/auth/oauth2.py:34 settings/serializers/auth/oidc.py:24
#: settings/serializers/feature.py:90
#: settings/serializers/feature.py:99
#: xpack/plugins/cloud/serializers/account_attrs.py:38
msgid "Client Secret"
msgstr "客户端密钥"
@ -6590,19 +6602,19 @@ msgstr "结束日期"
msgid "Announcement"
msgstr "公告"
#: settings/serializers/feature.py:47 settings/serializers/feature.py:50
#: settings/serializers/feature.py:56 settings/serializers/feature.py:59
msgid "Vault"
msgstr "启用 Vault"
#: settings/serializers/feature.py:53
#: settings/serializers/feature.py:62
msgid "Vault provider"
msgstr "保管库服务商"
#: settings/serializers/feature.py:57
#: settings/serializers/feature.py:66
msgid "Record limit"
msgstr "记录限制"
#: settings/serializers/feature.py:59
#: settings/serializers/feature.py:68
msgid ""
"If the specific value is less than 999 (default), the system will "
"automatically perform a task every night: check and delete historical "
@ -6612,83 +6624,83 @@ msgstr ""
"若特定数值小于999系统将在每日晚间自动执行任务检查并删除超出预定数量的历史"
"账号。如果该数值达到或超过999则不进行任何历史账号的删除操作。"
#: settings/serializers/feature.py:77
#: settings/serializers/feature.py:86
msgid "Mount Point"
msgstr "挂载点"
#: settings/serializers/feature.py:93
#: settings/serializers/feature.py:102
#: xpack/plugins/cloud/serializers/account_attrs.py:41
msgid "Tenant ID"
msgstr "租户 ID"
#: settings/serializers/feature.py:98 settings/serializers/feature.py:104
#: settings/serializers/feature.py:107 settings/serializers/feature.py:113
msgid "Chat AI"
msgstr "聊天 AI"
#: settings/serializers/feature.py:107
#: settings/serializers/feature.py:116
msgid "GPT Base URL"
msgstr "GPT 地址"
#: settings/serializers/feature.py:108
#: settings/serializers/feature.py:117
msgid "The base URL of the GPT service. For example: https://api.openai.com/v1"
msgstr "GPT 服务的基本 URL。例如https://api.openai.com/v1"
#: settings/serializers/feature.py:111 templates/_header_bar.html:96
#: settings/serializers/feature.py:120 templates/_header_bar.html:96
msgid "API Key"
msgstr "API Key"
#: settings/serializers/feature.py:115
#: settings/serializers/feature.py:124
msgid ""
"The proxy server address of the GPT service. For example: http://ip:port"
msgstr "GPT 服务的代理服务器地址。例如http://ip:port"
#: settings/serializers/feature.py:118
#: settings/serializers/feature.py:127
msgid "GPT Model"
msgstr "GPT 模型"
#: settings/serializers/feature.py:127
#: settings/serializers/feature.py:136
msgid "Approval without login"
msgstr "免登录审批"
#: settings/serializers/feature.py:128
#: settings/serializers/feature.py:137
msgid "Allow direct approval ticket without login"
msgstr "允许无需登录直接批准工单"
#: settings/serializers/feature.py:132
#: settings/serializers/feature.py:141
msgid "Period"
msgstr "时段"
#: settings/serializers/feature.py:133
#: settings/serializers/feature.py:142
msgid ""
"The default authorization time period when applying for assets via a ticket"
msgstr "工单申请资产的默认授权时间段"
#: settings/serializers/feature.py:136
#: settings/serializers/feature.py:145
msgid "hour"
msgstr "时"
#: settings/serializers/feature.py:137
#: settings/serializers/feature.py:146
msgid "Unit"
msgstr "单位"
#: settings/serializers/feature.py:137
#: settings/serializers/feature.py:146
msgid "The unit of period"
msgstr "执行周期"
#: settings/serializers/feature.py:146
#: settings/serializers/feature.py:155
msgid ""
"Allow users to execute batch commands in the Workbench - Job Center - Adhoc"
msgstr "允许用户在工作台 - 作业中心 - Adhoc 中执行批量命令"
#: settings/serializers/feature.py:150
#: settings/serializers/feature.py:159
msgid "Command blacklist"
msgstr "作业中心命令黑名单"
#: settings/serializers/feature.py:151
#: settings/serializers/feature.py:160
msgid "Command blacklist in Adhoc"
msgstr "作业中心命令黑名单"
#: settings/serializers/feature.py:156
#: settings/serializers/feature.py:165
#: terminal/models/virtualapp/provider.py:17
#: terminal/models/virtualapp/virtualapp.py:36
#: terminal/models/virtualapp/virtualapp.py:97
@ -6696,11 +6708,11 @@ msgstr "作业中心命令黑名单"
msgid "Virtual app"
msgstr "虚拟应用"
#: settings/serializers/feature.py:159
#: settings/serializers/feature.py:168
msgid "Virtual App"
msgstr "虚拟应用"
#: settings/serializers/feature.py:161
#: settings/serializers/feature.py:170
msgid ""
"Virtual applications, you can use the Linux operating system as an "
"application server in remote applications."
@ -7351,7 +7363,7 @@ msgstr "文档"
msgid "Commercial support"
msgstr "商业支持"
#: templates/_header_bar.html:85 users/forms/profile.py:43
#: templates/_header_bar.html:85 users/forms/profile.py:44
msgid "Profile"
msgstr "个人信息"
@ -9051,7 +9063,7 @@ msgstr "无法删除全部用户"
msgid "Create failed. The number of SSH keys has reached the limit"
msgstr "创建失败SSH密钥数量已达到上限"
#: users/forms/profile.py:48
#: users/forms/profile.py:49
msgid ""
"When enabled, you will enter the MFA binding process the next time you log "
"in. you can also directly bind in \"personal information -> quick "
@ -9060,11 +9072,11 @@ msgstr ""
"启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在 (个人信息->快速"
"修改->设置 MFA 多因子认证)中直接绑定!"
#: users/forms/profile.py:59
#: users/forms/profile.py:60
msgid "* Enable MFA to make the account more secure."
msgstr "* 启用 MFA 多因子认证,使账号更加安全。"
#: users/forms/profile.py:68
#: users/forms/profile.py:69
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
@ -9073,47 +9085,47 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息; (如:"
"设置复杂密码,并启用 MFA 多因子认证)"
#: users/forms/profile.py:82 users/serializers/preference/lina.py:21
#: users/forms/profile.py:83 users/serializers/preference/lina.py:21
msgid "New password"
msgstr "新密码"
#: users/forms/profile.py:87 users/serializers/preference/lina.py:26
#: users/forms/profile.py:88 users/serializers/preference/lina.py:26
msgid "Confirm password"
msgstr "确认密码"
#: users/forms/profile.py:95
#: users/forms/profile.py:96
msgid "Password does not match"
msgstr "密码不一致"
#: users/forms/profile.py:104
#: users/forms/profile.py:105
msgid "The phone number must contain an area code, for example, +86"
msgstr "手机号码必须包含区号,例如 +86"
#: users/forms/profile.py:120
#: users/forms/profile.py:123
msgid "Old password"
msgstr "原来密码"
#: users/forms/profile.py:130
#: users/forms/profile.py:133
msgid "Old password error"
msgstr "原来密码错误"
#: users/forms/profile.py:140
#: users/forms/profile.py:143
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
#: users/forms/profile.py:142
#: users/forms/profile.py:145
msgid "ssh public key"
msgstr "SSH公钥"
#: users/forms/profile.py:143
#: users/forms/profile.py:146
msgid "ssh-rsa AAAA..."
msgstr "ssh-rsa AAAA..."
#: users/forms/profile.py:144
#: users/forms/profile.py:147
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
#: users/forms/profile.py:157
#: users/forms/profile.py:160
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
@ -9422,12 +9434,12 @@ msgstr "MFA"
msgid "Multi-Factor Authentication"
msgstr "认证"
#: users/serializers/user.py:426
#: users/serializers/user.py:434
msgid ""
"* For security, only a partial of users is displayed. You can search for more"
msgstr "* 为安全起见,只显示部分用户。您可以搜索更多"
#: users/serializers/user.py:461
#: users/serializers/user.py:469
msgid "name not unique"
msgstr "名称重复"
@ -10608,7 +10620,7 @@ msgstr "企业专业版"
msgid "Ultimate edition"
msgstr "企业旗舰版"
#: xpack/plugins/license/models.py:101
#: xpack/plugins/license/models.py:100
msgid "FIT2CLOUD"
msgstr "飞致云"

@ -69,5 +69,7 @@
"VerifyCode": "Verify Code",
"WaitFileTransfer": "Wait file transfer to finish",
"WebSocketClosed": "WebSocket closed",
"Writable": "Writable"
"Writable": "Writable",
"UploadStart": "Upload start",
"UploadEnd": "Upload completed, please wait for further processing"
}

@ -69,5 +69,7 @@
"VerifyCode": "認証コード",
"WaitFileTransfer": "ファイル転送終了待ち",
"WebSocketClosed": "WebSocket 閉店",
"Writable": "書き込み可能"
"Writable": "書き込み可能",
"UploadStart": "アップロード開始",
"UploadEnd": "アップロードが完了しました。後の処理をお待ちください"
}

@ -65,6 +65,8 @@
"UploadSuccess": "上传成功",
"UploadTips": "将文件拖到此处,或点击上传",
"UploadTitle": "上传文件",
"UploadStart": "上传开始",
"UploadEnd": "上传已完成,请等待后续处理",
"User": "用户",
"VerifyCode": "验证码",
"WaitFileTransfer": "等待文件传输结束",

@ -69,5 +69,7 @@
"VerifyCode": "驗證碼",
"WaitFileTransfer": "等待文件傳輸結束",
"WebSocketClosed": "WebSocket 已關閉",
"Writable": "讀寫"
"Writable": "讀寫",
"UploadStart": "上傳開始",
"UploadEnd": "上傳已完成,請等待後續處理"
}

@ -177,6 +177,8 @@
"AwaitingMyApproval": "Assigned",
"Azure": "Azure (China)",
"Azure_Int": "Azure (International)",
"AzureKeyVault": "Azure vault",
"HashicorpVault": "HCP vault",
"Backup": "Backup",
"BackupAccountsHelpText": "Backup account information externally. it can be stored in an external system or sent via email, supporting segmented delivery.",
"BadConflictErrorMsg": "Refreshing, please try again later",
@ -651,7 +653,7 @@
"JobCenter": "Job center",
"JobCreate": "Create job",
"JobDetail": "Job details",
"JobExecutionLog": "Execution record",
"JobExecutionLog": "Executions",
"JobManagement": "Job List",
"JobUpdate": "Update the job",
"KingSoftCloud": "KingSoft cloud",
@ -1411,7 +1413,7 @@
"forceEnableMFAHelpText": "If force enable, user can not disable by themselves",
"removeWarningMsg": "Are you sure you want to remove",
"setVariable": "Set variable",
"JobsAudit": "Jobs audit",
"JobsAudit": "Job audits",
"JobList": "Job List",
"StopJobMsg": "Stop job successfully",
"ExtraArgsFormatError": "Format error, please enter according to the requirements",

@ -177,6 +177,8 @@
"AwaitingMyApproval": "私の承認待ち",
"Azure": "Azure(中国)",
"Azure_Int": "アジュール(インターナショナル)",
"AzureKeyVault": "Azure vault",
"HashicorpVault": "HCP vault",
"Backup": "バックアップ",
"BackupAccountsHelpText": "アカウント情報を外部にバックアップする。外部システムに保存するかメールを送信することもできます、セクション方式をサポートしています",
"BadConflictErrorMsg": "更新中です、しばらくお待ちください",

@ -177,6 +177,8 @@
"AwaitingMyApproval": "待我审批",
"Azure": "Azure (中国)",
"Azure_Int": "Azure (国际)",
"AzureKeyVault": "Azure vault",
"HashicorpVault": "HCP vault",
"Backup": "备份",
"BackupAccountsHelpText": "备份账号信息到外部。可以存储到外部系统或发送邮件,支持分段方式",
"BadConflictErrorMsg": "正在刷新中,请稍后再试",

@ -238,6 +238,8 @@
"AwaitingMyApproval": "待我審批",
"Azure": "Azure (中國)",
"Azure_Int": "Azure (國際)",
"AzureKeyVault": "Azure vault",
"HashicorpVault": "HCP vault",
"Backup": "備份",
"BackupAccountsHelpText": "備份帳號資訊至外部。可以儲存到外部系統或寄送郵件,支援分段方式",
"BadConflictErrorMsg": "正在刷新中,請稍後再試",

@ -18,11 +18,12 @@ if not XPACK_DISABLED:
XPACK_TEMPLATES_DIR = []
XPACK_CONTEXT_PROCESSOR = []
XPACK_LICENSE_IS_VALID = False
XPACK_LICENSE_EDITION = ""
XPACK_LICENSE_INFO = {
'corporation': corporation,
}
XPACK_LICENSE_CONTENT = ''
XPACK_LICENSE_CONTENT = 'community'
if XPACK_ENABLED:
from xpack.utils import get_xpack_templates_dir, get_xpack_context_processor

@ -101,7 +101,7 @@ class ResourceDownload(TemplateView):
MRD_VERSION=10.6.7
OPENSSH_VERSION=v9.4.0.0
TINKER_VERSION=v0.1.6
VIDEO_PLAYER_VERSION=0.1.9
VIDEO_PLAYER_VERSION=0.2.0
CLIENT_VERSION=v2.1.3
"""

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from common.api.generic import JMSBulkModelViewSet
from common.utils.http import is_true
@ -28,7 +29,7 @@ class AdHocViewSet(JMSBulkModelViewSet):
def check_object_permissions(self, request, obj):
if request.method != 'GET' and obj.creator != request.user:
self.permission_denied(
request, message={"detail": "Deleting other people's script is not allowed"}
request, message={"detail": _("Deleting other people's script is not allowed")}
)
return super().check_object_permissions(request, obj)

@ -105,6 +105,7 @@ class JobViewSet(OrgBulkModelViewSet):
def perform_update(self, serializer):
run_after_save = serializer.validated_data.pop('run_after_save', False)
self._parameters = serializer.validated_data.pop('parameters', None)
instance = serializer.save()
if run_after_save:
self.run_job(instance, serializer)
@ -227,9 +228,9 @@ class JobExecutionViewSet(OrgBulkModelViewSet):
try:
user = request.user
if user.has_perm("audits.view_joblog"):
instance = get_object_or_404(JobExecution, pk=task_id)
instance = get_object_or_404(JobExecution, task_id=task_id)
else:
instance = get_object_or_404(JobExecution, pk=task_id, creator=request.user)
instance = get_object_or_404(JobExecution, task_id=task_id, creator=request.user)
except Http404:
return Response(
{'error': _('The task is being created and cannot be interrupted. Please try again later.')},

@ -46,7 +46,7 @@ class PlaybookViewSet(JMSBulkModelViewSet):
def check_object_permissions(self, request, obj):
if request.method != 'GET' and obj.creator != request.user:
self.permission_denied(
request, message={"detail": "Deleting other people's playbook is not allowed"}
request, message={"detail": _("Deleting other people's playbook is not allowed")}
)
return super().check_object_permissions(request, obj)

@ -38,6 +38,18 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin, W
user = request.user if request else None
return user
def get_periodic_variable(self, variables):
periodic_variable = {}
for variable in variables:
periodic_variable[variable['var_name']] = variable['default_value']
return periodic_variable
def validate(self, attrs):
attrs = super().validate(attrs)
if attrs.get('is_periodic') is True:
attrs['periodic_variable'] = self.get_periodic_variable(attrs.get('variable', []))
return attrs
class Meta:
model = Job
read_only_fields = [

@ -4,9 +4,12 @@ import os
import aiofiles
from asgiref.sync import sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from http.cookies import SimpleCookie
from common.db.utils import close_old_connections
from common.utils import get_logger
from orgs.models import Organization
from orgs.utils import tmp_to_org, current_org
from rbac.builtin import BuiltinRole
from .ansible.utils import get_ansible_task_log_path
from .celery.utils import get_celery_task_log_path
@ -18,6 +21,8 @@ logger = get_logger(__name__)
class TaskLogWebsocket(AsyncJsonWebsocketConsumer):
disconnected = False
cookie = None
org = None
user_tasks = (
'ops.tasks.run_ops_job',
'ops.tasks.run_ops_job_execution',
@ -28,10 +33,25 @@ class TaskLogWebsocket(AsyncJsonWebsocketConsumer):
'ansible': get_ansible_task_log_path
}
def get_cookie(self):
try:
headers = self.scope['headers']
headers_dict = {key.decode('utf-8'): value.decode('utf-8') for key, value in headers}
cookie = SimpleCookie(headers_dict.get('cookie', ''))
except Exception as e:
cookie = SimpleCookie()
return cookie
def get_current_org(self):
oid = self.cookie.get('X-JMS-ORG')
return oid.value if oid else None
async def connect(self):
user = self.scope["user"]
if user.is_authenticated:
await self.accept()
self.cookie = self.get_cookie()
self.org = self.get_current_org()
else:
await self.close()
@ -51,10 +71,18 @@ class TaskLogWebsocket(AsyncJsonWebsocketConsumer):
@sync_to_async
def get_current_user_role_ids(self, user):
roles = user.system_roles.all() | user.org_roles.all()
with tmp_to_org(self.org):
org_roles = user.org_roles.all()
system_roles = user.system_roles.all()
roles = system_roles | org_roles
user_role_ids = set(map(str, roles.values_list('id', flat=True)))
return user_role_ids
@sync_to_async
def has_perms(self, user, perms):
with tmp_to_org(self.org):
return user.has_perms(perms)
async def receive_json(self, content, **kwargs):
task_id = content.get('task')
task = await self.get_task(task_id)
@ -71,8 +99,10 @@ class TaskLogWebsocket(AsyncJsonWebsocketConsumer):
user = self.scope['user']
user_role_ids = await self.get_current_user_role_ids(user)
has_admin_auditor_role = bool(admin_auditor_role_ids & user_role_ids)
if not has_admin_auditor_role and task.name in self.user_tasks and task.creator != user:
has_perms = await self.has_perms(user, ['audits.view_joblog'])
user_can_view = task.name in self.user_tasks and (task.creator == user or has_perms)
# (有管理员或审计员角色) 或者 (任务是用户自己创建的 或者 有查看任务日志权限), 其他情况没有权限
if not (has_admin_auditor_role or user_can_view):
await self.send_json({'message': 'No permission', 'task': task_id})
return

@ -64,7 +64,7 @@ extra_nodes_data = [
{'id': "tasks", "name": _("Task"), "pId": "view_setting"},
{'id': "license", "name": _("License"), "pId": "view_setting"},
{'id': "other", "name": _("Other"), "pId": "view_setting"},
{'id': "job_audit", "name": _("Job audit log"), "pId": "view_audit"},
{'id': "job_audit", "name": _("Job audit"), "pId": "view_audit"},
]
# 将 model 放到其它节点下,而不是本来的 app 中

@ -1,9 +1,9 @@
import uuid
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import VaultTypeChoices
from assets.const import Protocol
from common.serializers.fields import EncryptedField
from common.utils import date_expired_default
@ -43,7 +43,16 @@ class AnnouncementSettingSerializer(serializers.Serializer):
ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement"))
class VaultSettingSerializer(serializers.Serializer):
class BaseVaultSettingSerializer(serializers.Serializer):
def validate(self, data):
from accounts.signal_handlers import vault_pub_sub
data = super().validate(data)
vault_pub_sub.publish('vault')
return data
class VaultSettingSerializer(BaseVaultSettingSerializer, serializers.Serializer):
PREFIX_TITLE = _('Vault')
VAULT_ENABLED = serializers.BooleanField(
@ -65,7 +74,7 @@ class VaultSettingSerializer(serializers.Serializer):
)
class HashicorpKVSerializer(serializers.Serializer):
class HashicorpKVSerializer(BaseVaultSettingSerializer, serializers.Serializer):
PREFIX_TITLE = _('HCP Vault')
VAULT_HCP_HOST = serializers.CharField(
max_length=256, allow_blank=True, required=False, label=_('Host')
@ -78,7 +87,7 @@ class HashicorpKVSerializer(serializers.Serializer):
)
class AzureKVSerializer(serializers.Serializer):
class AzureKVSerializer(BaseVaultSettingSerializer, serializers.Serializer):
PREFIX_TITLE = _('Azure Key Vault')
VAULT_AZURE_HOST = serializers.CharField(
max_length=256, allow_blank=True, required=False, label=_('Host')

@ -50,10 +50,10 @@ p {
</div>
<div class="group">
<h2>JumpServer {% trans 'Offline video player' %} v0.1.9</h2>
<h2>JumpServer {% trans 'Offline video player' %} v{{ VIDEO_PLAYER_VERSION }}</h2>
<ul>
<li><a href="/download/public/JumpServer.Video.Player-{{ VIDEO_PLAYER_VERSION }}.dmg">jumpserver-video-player.dmg</a></li>
<li><a href="/download/public/JumpServer.Video.Player.Setup.{{ VIDEO_PLAYER_VERSION }}.exe">jumpserver-video-player.exe</a></li>
<li><a href="/download/public/JumpServerVideoPlayer-{{ VIDEO_PLAYER_VERSION }}.dmg">jumpserver-video-player.dmg</a></li>
<li><a href="/download/public/JumpServerVideoPlayer-{{ VIDEO_PLAYER_VERSION }}.exe">jumpserver-video-player.exe</a></li>
</ul>
</div>
</div>

Loading…
Cancel
Save