mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-16 17:12:53 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb75a468ed | ||
|
|
ed408fb739 | ||
|
|
d0bf815e9b | ||
|
|
1d4ea5dbe2 | ||
|
|
b5a92e5344 | ||
|
|
7dfd24ee2b | ||
|
|
33d846433a | ||
|
|
77669b0ba1 | ||
|
|
3c37c86a8e | ||
|
|
2d43c58524 | ||
|
|
95412d739a | ||
|
|
93b5876469 | ||
|
|
da02d1e456 | ||
|
|
5928b265b7 | ||
|
|
f46c043db5 | ||
|
|
cbc1ab411b | ||
|
|
5e03af7243 |
@@ -32,6 +32,8 @@ class SerializeApplicationToTreeNodeMixin:
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
def serialize_applications_with_org(self, applications):
|
def serialize_applications_with_org(self, applications):
|
||||||
|
if not applications:
|
||||||
|
return []
|
||||||
root_node = self.create_root_node()
|
root_node = self.create_root_node()
|
||||||
tree_nodes = [root_node]
|
tree_nodes = [root_node]
|
||||||
organizations = self.filter_organizations(applications)
|
organizations = self.filter_organizations(applications)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def migrate_system_assets_to_authbook(apps, schema_editor):
|
|||||||
system_users = system_user_model.objects.all()
|
system_users = system_user_model.objects.all()
|
||||||
for s in system_users:
|
for s in system_users:
|
||||||
while True:
|
while True:
|
||||||
systemuser_asset_relations = system_user_asset_model.objects.filter(systemuser=s)[:20]
|
systemuser_asset_relations = system_user_asset_model.objects.filter(systemuser=s)[:1000]
|
||||||
if not systemuser_asset_relations:
|
if not systemuser_asset_relations:
|
||||||
break
|
break
|
||||||
authbooks = []
|
authbooks = []
|
||||||
|
|||||||
@@ -94,25 +94,27 @@ class AuthBook(BaseUser, AbsConnectivity):
|
|||||||
i.private_key = self.private_key
|
i.private_key = self.private_key
|
||||||
i.public_key = self.public_key
|
i.public_key = self.public_key
|
||||||
i.comment = 'Update triggered by account {}'.format(self.id)
|
i.comment = 'Update triggered by account {}'.format(self.id)
|
||||||
i.save(update_fields=['password', 'private_key', 'public_key'])
|
|
||||||
|
# 不触发post_save信号
|
||||||
|
self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key'])
|
||||||
|
|
||||||
def remove_asset_admin_user_if_need(self):
|
def remove_asset_admin_user_if_need(self):
|
||||||
if not self.asset or not self.asset.admin_user:
|
if not self.asset or not self.systemuser:
|
||||||
return
|
return
|
||||||
if not self.systemuser.is_admin_user:
|
if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser:
|
||||||
return
|
return
|
||||||
logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser))
|
|
||||||
self.asset.admin_user = None
|
self.asset.admin_user = None
|
||||||
self.asset.save()
|
self.asset.save()
|
||||||
|
logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser))
|
||||||
|
|
||||||
def update_asset_admin_user_if_need(self):
|
def update_asset_admin_user_if_need(self):
|
||||||
if not self.systemuser or not self.systemuser.is_admin_user:
|
if not self.asset or not self.systemuser:
|
||||||
return
|
return
|
||||||
if not self.asset or self.asset.admin_user == self.systemuser:
|
if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser:
|
||||||
return
|
return
|
||||||
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
|
|
||||||
self.asset.admin_user = self.systemuser
|
self.asset.admin_user = self.systemuser
|
||||||
self.asset.save()
|
self.asset.save()
|
||||||
|
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.smart_name
|
return self.smart_name
|
||||||
|
|||||||
@@ -34,8 +34,10 @@ def on_authbook_post_delete(sender, instance, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=AuthBook)
|
@receiver(post_save, sender=AuthBook)
|
||||||
def on_authbook_post_create(sender, instance, **kwargs):
|
def on_authbook_post_create(sender, instance, created, **kwargs):
|
||||||
instance.sync_to_system_user_account()
|
instance.sync_to_system_user_account()
|
||||||
|
if created:
|
||||||
|
# 只在创建时进行更新资产的管理用户
|
||||||
instance.update_asset_admin_user_if_need()
|
instance.update_asset_admin_user_if_need()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
|
||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org
|
||||||
|
from assets.models import AuthBook
|
||||||
|
|
||||||
__all__ = ['add_nodes_assets_to_system_users']
|
__all__ = ['add_nodes_assets_to_system_users']
|
||||||
|
|
||||||
@@ -15,4 +16,12 @@ def add_nodes_assets_to_system_users(nodes_keys, system_users):
|
|||||||
nodes = Node.objects.filter(key__in=nodes_keys)
|
nodes = Node.objects.filter(key__in=nodes_keys)
|
||||||
assets = Node.get_nodes_all_assets(*nodes)
|
assets = Node.get_nodes_all_assets(*nodes)
|
||||||
for system_user in system_users:
|
for system_user in system_users:
|
||||||
system_user.assets.add(*tuple(assets))
|
""" 解决资产和节点进行关联时,已经关联过的节点不会触发 authbook post_save 信号,
|
||||||
|
无法更新节点下所有资产的管理用户的问题 """
|
||||||
|
for asset in assets:
|
||||||
|
defaults = {'asset': asset, 'systemuser': system_user, 'org_id': asset.org_id}
|
||||||
|
instance, created = AuthBook.objects.update_or_create(
|
||||||
|
defaults=defaults, asset=asset, systemuser=system_user
|
||||||
|
)
|
||||||
|
# 只要关联都需要更新资产的管理用户
|
||||||
|
instance.update_asset_admin_user_if_need()
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
if not current_org.is_default():
|
if current_org.is_root():
|
||||||
|
return queryset
|
||||||
users = self.get_org_members()
|
users = self.get_org_members()
|
||||||
queryset = queryset.filter(username__in=users)
|
queryset = queryset.filter(username__in=users)
|
||||||
return queryset
|
return queryset
|
||||||
|
|||||||
@@ -35,13 +35,14 @@ class UserLoginLogSerializer(serializers.ModelSerializer):
|
|||||||
fields_mini = ['id']
|
fields_mini = ['id']
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'username', 'type', 'type_display', 'ip', 'city', 'user_agent',
|
'username', 'type', 'type_display', 'ip', 'city', 'user_agent',
|
||||||
'mfa', 'mfa_display', 'reason', 'backend',
|
'mfa', 'mfa_display', 'reason', 'reason_display', 'backend',
|
||||||
'status', 'status_display',
|
'status', 'status_display',
|
||||||
'datetime',
|
'datetime',
|
||||||
]
|
]
|
||||||
fields = fields_small
|
fields = fields_small
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"user_agent": {'label': _('User agent')}
|
"user_agent": {'label': _('User agent')},
|
||||||
|
"reason_display": {'label': _('Reason')}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div style="line-height: 17px;margin-bottom: 20px;color: #999999;">
|
<div style="line-height: 17px;margin-bottom: 20px;color: #999999;">
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<p class="help-block">
|
<p class="help-block red-fonts">
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
{{ form.non_field_errors.as_text }}
|
{{ form.non_field_errors.as_text }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class DingTalkQRMixin(PermissionsMixin, View):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
def get_verify_state_failed_response(self, redirect_uri):
|
||||||
msg = _("You've been hacked")
|
msg = _("The system configuration is incorrect. Please contact your administrator")
|
||||||
return self.get_failed_reponse(redirect_uri, msg, msg)
|
return self.get_failed_reponse(redirect_uri, msg, msg)
|
||||||
|
|
||||||
def get_qr_url(self, redirect_uri):
|
def get_qr_url(self, redirect_uri):
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class FeiShuQRMixin(PermissionsMixin, View):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
def get_verify_state_failed_response(self, redirect_uri):
|
||||||
msg = _("You've been hacked")
|
msg = _("The system configuration is incorrect. Please contact your administrator")
|
||||||
return self.get_failed_reponse(redirect_uri, msg, msg)
|
return self.get_failed_reponse(redirect_uri, msg, msg)
|
||||||
|
|
||||||
def get_qr_url(self, redirect_uri):
|
def get_qr_url(self, redirect_uri):
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||||||
self.request.session.delete_test_cookie()
|
self.request.session.delete_test_cookie()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
|
||||||
self.check_user_auth(decrypt_passwd=True)
|
self.check_user_auth(decrypt_passwd=True)
|
||||||
except errors.AuthFailedError as e:
|
except errors.AuthFailedError as e:
|
||||||
form.add_error(None, e.msg)
|
form.add_error(None, e.msg)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class WeComQRMixin(PermissionsMixin, View):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
def get_verify_state_failed_response(self, redirect_uri):
|
||||||
msg = _("You've been hacked")
|
msg = _("The system configuration is incorrect. Please contact your administrator")
|
||||||
return self.get_failed_reponse(redirect_uri, msg, msg)
|
return self.get_failed_reponse(redirect_uri, msg, msg)
|
||||||
|
|
||||||
def get_qr_url(self, redirect_uri):
|
def get_qr_url(self, redirect_uri):
|
||||||
|
|||||||
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-09-16 19:25+0800\n"
|
"POT-Creation-Date: 2021-10-15 10:45+0800\n"
|
||||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||||
@@ -281,7 +281,7 @@ msgstr "应用管理"
|
|||||||
#: applications/serializers/application.py:88 assets/models/label.py:21
|
#: applications/serializers/application.py:88 assets/models/label.py:21
|
||||||
#: perms/models/application_permission.py:20
|
#: perms/models/application_permission.py:20
|
||||||
#: perms/serializers/application/user_permission.py:33
|
#: perms/serializers/application/user_permission.py:33
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:24
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:25
|
#: xpack/plugins/change_auth_plan/models/app.py:25
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "类别"
|
msgstr "类别"
|
||||||
@@ -292,7 +292,7 @@ msgstr "类别"
|
|||||||
#: perms/serializers/application/user_permission.py:34
|
#: perms/serializers/application/user_permission.py:34
|
||||||
#: terminal/models/storage.py:55 terminal/models/storage.py:116
|
#: terminal/models/storage.py:55 terminal/models/storage.py:116
|
||||||
#: tickets/models/flow.py:51 tickets/models/ticket.py:48
|
#: tickets/models/flow.py:51 tickets/models/ticket.py:48
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:31
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:28
|
#: xpack/plugins/change_auth_plan/models/app.py:28
|
||||||
#: xpack/plugins/change_auth_plan/models/app.py:148
|
#: xpack/plugins/change_auth_plan/models/app.py:148
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
@@ -310,7 +310,7 @@ msgstr ""
|
|||||||
#: applications/serializers/application.py:59
|
#: applications/serializers/application.py:59
|
||||||
#: applications/serializers/application.py:89 assets/serializers/label.py:13
|
#: applications/serializers/application.py:89 assets/serializers/label.py:13
|
||||||
#: perms/serializers/application/permission.py:16
|
#: perms/serializers/application/permission.py:16
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:28
|
||||||
msgid "Category display"
|
msgid "Category display"
|
||||||
msgstr "类别名称"
|
msgstr "类别名称"
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ msgstr "类别名称"
|
|||||||
#: applications/serializers/application.py:91
|
#: applications/serializers/application.py:91
|
||||||
#: assets/serializers/system_user.py:26 audits/serializers.py:29
|
#: assets/serializers/system_user.py:26 audits/serializers.py:29
|
||||||
#: perms/serializers/application/permission.py:17
|
#: perms/serializers/application/permission.py:17
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:35
|
||||||
#: tickets/serializers/ticket/ticket.py:22
|
#: tickets/serializers/ticket/ticket.py:22
|
||||||
#: tickets/serializers/ticket/ticket.py:168
|
#: tickets/serializers/ticket/ticket.py:168
|
||||||
msgid "Type display"
|
msgid "Type display"
|
||||||
@@ -1091,7 +1091,7 @@ msgstr "成功"
|
|||||||
|
|
||||||
#: audits/models.py:43 ops/models/command.py:30 perms/models/base.py:49
|
#: audits/models.py:43 ops/models/command.py:30 perms/models/base.py:49
|
||||||
#: terminal/models/session.py:52
|
#: terminal/models/session.py:52
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:57
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:47
|
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:47
|
||||||
#: xpack/plugins/change_auth_plan/models/base.py:105
|
#: xpack/plugins/change_auth_plan/models/base.py:105
|
||||||
#: xpack/plugins/change_auth_plan/models/base.py:189
|
#: xpack/plugins/change_auth_plan/models/base.py:189
|
||||||
@@ -1848,8 +1848,9 @@ msgstr "钉钉错误"
|
|||||||
|
|
||||||
#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:52
|
#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:52
|
||||||
#: authentication/views/wecom.py:56
|
#: authentication/views/wecom.py:56
|
||||||
msgid "You've been hacked"
|
msgid ""
|
||||||
msgstr "你被攻击了"
|
"The system configuration is incorrect. Please contact your administrator"
|
||||||
|
msgstr "系统配置错误,请联系管理员"
|
||||||
|
|
||||||
#: authentication/views/dingtalk.py:92
|
#: authentication/views/dingtalk.py:92
|
||||||
msgid "DingTalk is already bound"
|
msgid "DingTalk is already bound"
|
||||||
@@ -2478,7 +2479,7 @@ msgid "User group"
|
|||||||
msgstr "用户组"
|
msgstr "用户组"
|
||||||
|
|
||||||
#: perms/models/base.py:50
|
#: perms/models/base.py:50
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:60
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50
|
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50
|
||||||
#: users/models/user.py:643
|
#: users/models/user.py:643
|
||||||
msgid "Date expired"
|
msgid "Date expired"
|
||||||
@@ -2554,7 +2555,7 @@ msgstr "测试手机号 该字段是必填项。"
|
|||||||
msgid "Test success"
|
msgid "Test success"
|
||||||
msgstr "测试成功"
|
msgstr "测试成功"
|
||||||
|
|
||||||
#: settings/api/email.py:21
|
#: settings/api/email.py:22
|
||||||
msgid "Test mail sent to {}, please check"
|
msgid "Test mail sent to {}, please check"
|
||||||
msgstr "邮件已经发送{}, 请检查"
|
msgstr "邮件已经发送{}, 请检查"
|
||||||
|
|
||||||
@@ -2828,7 +2829,7 @@ msgid "SMS provider"
|
|||||||
msgstr "短信服务商"
|
msgstr "短信服务商"
|
||||||
|
|
||||||
#: settings/serializers/auth/sms.py:17 settings/serializers/auth/sms.py:35
|
#: settings/serializers/auth/sms.py:17 settings/serializers/auth/sms.py:35
|
||||||
#: settings/serializers/auth/sms.py:43 settings/serializers/email.py:69
|
#: settings/serializers/auth/sms.py:43 settings/serializers/email.py:63
|
||||||
msgid "Signature"
|
msgid "Signature"
|
||||||
msgstr "签名"
|
msgstr "签名"
|
||||||
|
|
||||||
@@ -2919,89 +2920,89 @@ msgstr "上传下载"
|
|||||||
msgid "Cloud sync record keep days"
|
msgid "Cloud sync record keep days"
|
||||||
msgstr "云同步记录"
|
msgstr "云同步记录"
|
||||||
|
|
||||||
#: settings/serializers/email.py:24
|
#: settings/serializers/email.py:18
|
||||||
msgid "SMTP host"
|
msgid "SMTP host"
|
||||||
msgstr "SMTP 主机"
|
msgstr "SMTP 主机"
|
||||||
|
|
||||||
#: settings/serializers/email.py:25
|
#: settings/serializers/email.py:19
|
||||||
msgid "SMTP port"
|
msgid "SMTP port"
|
||||||
msgstr "SMTP 端口"
|
msgstr "SMTP 端口"
|
||||||
|
|
||||||
#: settings/serializers/email.py:26
|
#: settings/serializers/email.py:20
|
||||||
msgid "SMTP account"
|
msgid "SMTP account"
|
||||||
msgstr "SMTP 账号"
|
msgstr "SMTP 账号"
|
||||||
|
|
||||||
#: settings/serializers/email.py:28
|
#: settings/serializers/email.py:22
|
||||||
msgid "SMTP password"
|
msgid "SMTP password"
|
||||||
msgstr "SMTP 密码"
|
msgstr "SMTP 密码"
|
||||||
|
|
||||||
#: settings/serializers/email.py:29
|
#: settings/serializers/email.py:23
|
||||||
msgid "Tips: Some provider use token except password"
|
msgid "Tips: Some provider use token except password"
|
||||||
msgstr "提示:一些邮件提供商需要输入的是授权码"
|
msgstr "提示:一些邮件提供商需要输入的是授权码"
|
||||||
|
|
||||||
#: settings/serializers/email.py:32
|
#: settings/serializers/email.py:26
|
||||||
msgid "Send user"
|
msgid "Send user"
|
||||||
msgstr "发件人"
|
msgstr "发件人"
|
||||||
|
|
||||||
#: settings/serializers/email.py:33
|
#: settings/serializers/email.py:27
|
||||||
msgid "Tips: Send mail account, default SMTP account as the send account"
|
msgid "Tips: Send mail account, default SMTP account as the send account"
|
||||||
msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号"
|
msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号"
|
||||||
|
|
||||||
#: settings/serializers/email.py:36
|
#: settings/serializers/email.py:30
|
||||||
msgid "Test recipient"
|
msgid "Test recipient"
|
||||||
msgstr "测试收件人"
|
msgstr "测试收件人"
|
||||||
|
|
||||||
#: settings/serializers/email.py:37
|
#: settings/serializers/email.py:31
|
||||||
msgid "Tips: Used only as a test mail recipient"
|
msgid "Tips: Used only as a test mail recipient"
|
||||||
msgstr "提示:仅用来作为测试邮件收件人"
|
msgstr "提示:仅用来作为测试邮件收件人"
|
||||||
|
|
||||||
#: settings/serializers/email.py:40
|
#: settings/serializers/email.py:34
|
||||||
msgid "Use SSL"
|
msgid "Use SSL"
|
||||||
msgstr "使用 SSL"
|
msgstr "使用 SSL"
|
||||||
|
|
||||||
#: settings/serializers/email.py:41
|
#: settings/serializers/email.py:35
|
||||||
msgid "If SMTP port is 465, may be select"
|
msgid "If SMTP port is 465, may be select"
|
||||||
msgstr "如果SMTP端口是465,通常需要启用 SSL"
|
msgstr "如果SMTP端口是465,通常需要启用 SSL"
|
||||||
|
|
||||||
#: settings/serializers/email.py:44
|
#: settings/serializers/email.py:38
|
||||||
msgid "Use TLS"
|
msgid "Use TLS"
|
||||||
msgstr "使用 TLS"
|
msgstr "使用 TLS"
|
||||||
|
|
||||||
#: settings/serializers/email.py:45
|
#: settings/serializers/email.py:39
|
||||||
msgid "If SMTP port is 587, may be select"
|
msgid "If SMTP port is 587, may be select"
|
||||||
msgstr "如果SMTP端口是587,通常需要启用 TLS"
|
msgstr "如果SMTP端口是587,通常需要启用 TLS"
|
||||||
|
|
||||||
#: settings/serializers/email.py:48
|
#: settings/serializers/email.py:42
|
||||||
msgid "Subject prefix"
|
msgid "Subject prefix"
|
||||||
msgstr "主题前缀"
|
msgstr "主题前缀"
|
||||||
|
|
||||||
#: settings/serializers/email.py:55
|
#: settings/serializers/email.py:49
|
||||||
msgid "Create user email subject"
|
msgid "Create user email subject"
|
||||||
msgstr "邮件主题"
|
msgstr "邮件主题"
|
||||||
|
|
||||||
#: settings/serializers/email.py:56
|
#: settings/serializers/email.py:50
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tips: When creating a user, send the subject of the email (eg:Create account "
|
"Tips: When creating a user, send the subject of the email (eg:Create account "
|
||||||
"successfully)"
|
"successfully)"
|
||||||
msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)"
|
msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)"
|
||||||
|
|
||||||
#: settings/serializers/email.py:60
|
#: settings/serializers/email.py:54
|
||||||
msgid "Create user honorific"
|
msgid "Create user honorific"
|
||||||
msgstr "邮件的敬语"
|
msgstr "邮件的敬语"
|
||||||
|
|
||||||
#: settings/serializers/email.py:61
|
#: settings/serializers/email.py:55
|
||||||
msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)"
|
msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)"
|
||||||
msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)"
|
msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)"
|
||||||
|
|
||||||
#: settings/serializers/email.py:65
|
#: settings/serializers/email.py:59
|
||||||
msgid "Create user email content"
|
msgid "Create user email content"
|
||||||
msgstr "邮件的内容"
|
msgstr "邮件的内容"
|
||||||
|
|
||||||
#: settings/serializers/email.py:66
|
#: settings/serializers/email.py:60
|
||||||
msgid "Tips:When creating a user, send the content of the email"
|
msgid "Tips:When creating a user, send the content of the email"
|
||||||
msgstr "提示: 创建用户时,发送设置密码邮件的内容"
|
msgstr "提示: 创建用户时,发送设置密码邮件的内容"
|
||||||
|
|
||||||
#: settings/serializers/email.py:70
|
#: settings/serializers/email.py:64
|
||||||
msgid "Tips: Email signature (eg:jumpserver)"
|
msgid "Tips: Email signature (eg:jumpserver)"
|
||||||
msgstr "邮件署名 (如:jumpserver)"
|
msgstr "邮件署名 (如:jumpserver)"
|
||||||
|
|
||||||
@@ -4543,34 +4544,34 @@ msgstr "你有一个新的工单, 申请人 - {}"
|
|||||||
msgid "Your ticket has been processed, processor - {}"
|
msgid "Your ticket has been processed, processor - {}"
|
||||||
msgstr "你的工单已被处理, 处理人 - {}"
|
msgstr "你的工单已被处理, 处理人 - {}"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:18
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:18
|
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:18
|
||||||
msgid "Apply name"
|
msgid "Apply name"
|
||||||
msgstr "应用名称"
|
msgstr "应用名称"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:37
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:39
|
||||||
msgid "Apply applications"
|
msgid "Apply applications"
|
||||||
msgstr "申请应用"
|
msgstr "申请应用"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:42
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:44
|
||||||
msgid "Apply applications display"
|
msgid "Apply applications display"
|
||||||
msgstr "应用名称名称"
|
msgstr "应用名称名称"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:46
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:48
|
||||||
msgid "Apply system users"
|
msgid "Apply system users"
|
||||||
msgstr "系统用户"
|
msgstr "系统用户"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:51
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:53
|
||||||
msgid "Apply system user display"
|
msgid "Apply system user display"
|
||||||
msgstr "批准的系统用户名称"
|
msgstr "批准的系统用户名称"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:71
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:73
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:63
|
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:63
|
||||||
#: tickets/serializers/ticket/ticket.py:127
|
#: tickets/serializers/ticket/ticket.py:127
|
||||||
msgid "Permission named `{}` already exists"
|
msgid "Permission named `{}` already exists"
|
||||||
msgstr "授权名称 `{}` 已存在"
|
msgstr "授权名称 `{}` 已存在"
|
||||||
|
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:80
|
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:89
|
||||||
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:72
|
#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:72
|
||||||
msgid "The expiration date should be greater than the start date"
|
msgid "The expiration date should be greater than the start date"
|
||||||
msgstr "过期时间要大于开始时间"
|
msgstr "过期时间要大于开始时间"
|
||||||
@@ -5686,7 +5687,7 @@ msgstr "* 新密码不能是最近 {} 次的密码"
|
|||||||
msgid "Reset password success, return to login page"
|
msgid "Reset password success, return to login page"
|
||||||
msgstr "重置密码成功,返回到登录页面"
|
msgstr "重置密码成功,返回到登录页面"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/api/app.py:112
|
#: xpack/plugins/change_auth_plan/api/app.py:113
|
||||||
#: xpack/plugins/change_auth_plan/api/asset.py:100
|
#: xpack/plugins/change_auth_plan/api/asset.py:100
|
||||||
msgid "The parameter 'action' must be [{}]"
|
msgid "The parameter 'action' must be [{}]"
|
||||||
msgstr "参数 'action' 必须是 [{}]"
|
msgstr "参数 'action' 必须是 [{}]"
|
||||||
@@ -6071,48 +6072,68 @@ msgid "AF-Johannesburg"
|
|||||||
msgstr "非洲-约翰内斯堡"
|
msgstr "非洲-约翰内斯堡"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:36
|
#: xpack/plugins/cloud/providers/huaweicloud.py:36
|
||||||
msgid "AP-Bangkok"
|
msgid "CN North-Beijing4"
|
||||||
msgstr "亚太-曼谷"
|
msgstr "华北-北京4"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:37
|
#: xpack/plugins/cloud/providers/huaweicloud.py:37
|
||||||
msgid "AP-Hong Kong"
|
msgid "CN North-Beijing1"
|
||||||
msgstr "亚太-香港"
|
msgstr "华北-北京1"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:38
|
#: xpack/plugins/cloud/providers/huaweicloud.py:38
|
||||||
msgid "AP-Singapore"
|
msgid "CN East-Shanghai2"
|
||||||
msgstr "亚太-新加坡"
|
msgstr "华东-上海2"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:39
|
#: xpack/plugins/cloud/providers/huaweicloud.py:39
|
||||||
msgid "CN East-Shanghai1"
|
msgid "CN East-Shanghai1"
|
||||||
msgstr "华东-上海1"
|
msgstr "华东-上海1"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:40
|
#: xpack/plugins/cloud/providers/huaweicloud.py:40
|
||||||
msgid "CN East-Shanghai2"
|
|
||||||
msgstr "华东-上海2"
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:41
|
|
||||||
msgid "CN North-Beijing1"
|
|
||||||
msgstr "华北-北京1"
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:42
|
|
||||||
msgid "CN North-Beijing4"
|
|
||||||
msgstr "华北-北京4"
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:43
|
|
||||||
msgid "CN Northeast-Dalian"
|
|
||||||
msgstr "华北-大连"
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:44
|
|
||||||
msgid "CN South-Guangzhou"
|
msgid "CN South-Guangzhou"
|
||||||
msgstr "华南-广州"
|
msgstr "华南-广州"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:41
|
||||||
|
msgid "LA-Mexico City1"
|
||||||
|
msgstr "拉美-墨西哥城一"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:42
|
||||||
|
msgid "LA-Santiago"
|
||||||
|
msgstr "拉美-圣地亚哥"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:43
|
||||||
|
msgid "LA-Sao Paulo1"
|
||||||
|
msgstr "拉美-圣保罗一"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:44
|
||||||
|
msgid "EU-Paris"
|
||||||
|
msgstr "欧洲-巴黎"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:45
|
#: xpack/plugins/cloud/providers/huaweicloud.py:45
|
||||||
msgid "CN Southwest-Guiyang1"
|
msgid "CN Southwest-Guiyang1"
|
||||||
msgstr "西南-贵阳1"
|
msgstr "西南-贵阳1"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/providers/huaweicloud.py:46
|
#: xpack/plugins/cloud/providers/huaweicloud.py:46
|
||||||
msgid "EU-Paris"
|
msgid "AP-Bangkok"
|
||||||
msgstr "欧洲-巴黎"
|
msgstr "亚太-曼谷"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:47
|
||||||
|
msgid "AP-Singapore"
|
||||||
|
msgstr "亚太-新加坡"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:48
|
||||||
|
msgid "CN-Hong Kong"
|
||||||
|
msgstr "中国-香港"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:50
|
||||||
|
msgid "CN Northeast-Dalian"
|
||||||
|
msgstr "华北-大连"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:51
|
||||||
|
msgid "CN North-Ulanqab1"
|
||||||
|
msgstr "华北-乌兰察布一"
|
||||||
|
|
||||||
|
#: xpack/plugins/cloud/providers/huaweicloud.py:52
|
||||||
|
msgid "CN South-Guangzhou-InvitationOnly"
|
||||||
|
msgstr "华南-广州-友好用户环境"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers/account_attrs.py:13
|
#: xpack/plugins/cloud/serializers/account_attrs.py:13
|
||||||
msgid "AccessKey ID"
|
msgid "AccessKey ID"
|
||||||
@@ -6134,16 +6155,16 @@ msgstr "租户 ID"
|
|||||||
msgid "Subscription ID"
|
msgid "Subscription ID"
|
||||||
msgstr "订阅 ID"
|
msgstr "订阅 ID"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers/account_attrs.py:81
|
#: xpack/plugins/cloud/serializers/account_attrs.py:87
|
||||||
#: xpack/plugins/cloud/serializers/account_attrs.py:86
|
#: xpack/plugins/cloud/serializers/account_attrs.py:92
|
||||||
msgid "API Endpoint"
|
msgid "API Endpoint"
|
||||||
msgstr "API 端点"
|
msgstr "API 端点"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers/account_attrs.py:92
|
#: xpack/plugins/cloud/serializers/account_attrs.py:98
|
||||||
msgid "Service account key"
|
msgid "Service account key"
|
||||||
msgstr "账户密钥"
|
msgstr "账户密钥"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers/account_attrs.py:93
|
#: xpack/plugins/cloud/serializers/account_attrs.py:99
|
||||||
msgid "The file is in JSON format"
|
msgid "The file is in JSON format"
|
||||||
msgstr "JSON 格式的文件"
|
msgstr "JSON 格式的文件"
|
||||||
|
|
||||||
@@ -6252,5 +6273,8 @@ msgstr "旗舰版"
|
|||||||
msgid "Community edition"
|
msgid "Community edition"
|
||||||
msgstr "社区版"
|
msgstr "社区版"
|
||||||
|
|
||||||
|
#~ msgid "You've been hacked"
|
||||||
|
#~ msgstr "你被攻击了"
|
||||||
|
|
||||||
#~ msgid "Only "
|
#~ msgid "Only "
|
||||||
#~ msgstr "仅能从用户配置来源登录"
|
#~ msgstr "仅能从用户配置来源登录"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import threading
|
|||||||
import json
|
import json
|
||||||
from redis.exceptions import ConnectionError
|
from redis.exceptions import ConnectionError
|
||||||
from channels.generic.websocket import JsonWebsocketConsumer
|
from channels.generic.websocket import JsonWebsocketConsumer
|
||||||
|
from django.db import close_old_connections
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .site_msg import SiteMessageUtil
|
from .site_msg import SiteMessageUtil
|
||||||
@@ -65,8 +66,11 @@ class SiteMsgWebsocket(JsonWebsocketConsumer):
|
|||||||
logger.debug('Decode json error: ', e)
|
logger.debug('Decode json error: ', e)
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
logger.debug('Redis chan closed')
|
logger.debug('Redis chan closed')
|
||||||
|
finally:
|
||||||
|
close_old_connections()
|
||||||
|
|
||||||
def disconnect(self, close_code):
|
def disconnect(self, close_code):
|
||||||
if self.chan is not None:
|
if self.chan is not None:
|
||||||
self.chan.close()
|
self.chan.close()
|
||||||
self.close()
|
self.close()
|
||||||
|
close_old_connections()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from common.permissions import IsSuperUser
|
from common.permissions import IsSuperUser
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
@@ -24,14 +25,15 @@ class MailTestingAPI(APIView):
|
|||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
email_host = serializer.validated_data['EMAIL_HOST']
|
# 测试邮件时,邮件服务器信息从配置中获取
|
||||||
email_port = serializer.validated_data['EMAIL_PORT']
|
email_host = settings.EMAIL_HOST
|
||||||
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
|
email_port = settings.EMAIL_PORT
|
||||||
email_host_password = serializer.validated_data['EMAIL_HOST_PASSWORD']
|
email_host_user = settings.EMAIL_HOST_USER
|
||||||
email_from = serializer.validated_data["EMAIL_FROM"]
|
email_host_password = settings.EMAIL_HOST_PASSWORD
|
||||||
email_recipient = serializer.validated_data["EMAIL_RECIPIENT"]
|
email_from = serializer.validated_data.get('EMAIL_FROM')
|
||||||
email_use_ssl = serializer.validated_data['EMAIL_USE_SSL']
|
email_use_ssl = settings.EMAIL_USE_SSL
|
||||||
email_use_tls = serializer.validated_data['EMAIL_USE_TLS']
|
email_use_tls = settings.EMAIL_USE_TLS
|
||||||
|
email_recipient = serializer.validated_data.get('EMAIL_RECIPIENT')
|
||||||
|
|
||||||
# 设置 settings 的值,会导致动态配置在当前进程失效
|
# 设置 settings 的值,会导致动态配置在当前进程失效
|
||||||
# for k, v in serializer.validated_data.items():
|
# for k, v in serializer.validated_data.items():
|
||||||
|
|||||||
@@ -8,14 +8,8 @@ __all__ = ['MailTestSerializer', 'EmailSettingSerializer', 'EmailContentSettingS
|
|||||||
|
|
||||||
|
|
||||||
class MailTestSerializer(serializers.Serializer):
|
class MailTestSerializer(serializers.Serializer):
|
||||||
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
|
|
||||||
EMAIL_PORT = serializers.IntegerField(default=25, min_value=1, max_value=65535)
|
|
||||||
EMAIL_HOST_USER = serializers.CharField(max_length=1024)
|
|
||||||
EMAIL_HOST_PASSWORD = serializers.CharField(required=False, allow_blank=True)
|
|
||||||
EMAIL_FROM = serializers.CharField(required=False, allow_blank=True)
|
EMAIL_FROM = serializers.CharField(required=False, allow_blank=True)
|
||||||
EMAIL_RECIPIENT = serializers.CharField(required=False, allow_blank=True)
|
EMAIL_RECIPIENT = serializers.CharField(required=False, allow_blank=True)
|
||||||
EMAIL_USE_SSL = serializers.BooleanField(default=False)
|
|
||||||
EMAIL_USE_TLS = serializers.BooleanField(default=False)
|
|
||||||
|
|
||||||
|
|
||||||
class EmailSettingSerializer(serializers.Serializer):
|
class EmailSettingSerializer(serializers.Serializer):
|
||||||
|
|||||||
@@ -206,10 +206,12 @@ class Ticket(CommonModelMixin, OrgModelMixin):
|
|||||||
self.save()
|
self.save()
|
||||||
post_change_ticket_action.send(sender=self.__class__, ticket=self, action=action)
|
post_change_ticket_action.send(sender=self.__class__, ticket=self, action=action)
|
||||||
|
|
||||||
# ticket
|
def has_current_assignee(self, assignee):
|
||||||
def has_assignee(self, assignee):
|
|
||||||
return self.ticket_steps.filter(ticket_assignees__assignee=assignee, level=self.approval_step).exists()
|
return self.ticket_steps.filter(ticket_assignees__assignee=assignee, level=self.approval_step).exists()
|
||||||
|
|
||||||
|
def has_all_assignee(self, assignee):
|
||||||
|
return self.ticket_steps.filter(ticket_assignees__assignee=assignee).exists()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_related_tickets(cls, user):
|
def get_user_related_tickets(cls, user):
|
||||||
queries = Q(applicant=user) | Q(ticket_steps__ticket_assignees__assignee=user)
|
queries = Q(applicant=user) | Q(ticket_steps__ticket_assignees__assignee=user)
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ class IsApplicant(permissions.BasePermission):
|
|||||||
|
|
||||||
class IsAssignee(permissions.BasePermission):
|
class IsAssignee(permissions.BasePermission):
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
return view.ticket.has_assignee(request.user)
|
return view.ticket.has_all_assignee(request.user)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from rest_framework import permissions
|
|||||||
|
|
||||||
class IsAssignee(permissions.BasePermission):
|
class IsAssignee(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
return obj.has_assignee(request.user)
|
return obj.has_current_assignee(request.user)
|
||||||
|
|
||||||
|
|
||||||
class IsApplicant(permissions.BasePermission):
|
class IsApplicant(permissions.BasePermission):
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ class MFAMixin:
|
|||||||
methods = []
|
methods = []
|
||||||
if self.otp_secret_key:
|
if self.otp_secret_key:
|
||||||
methods.append(MFAType.OTP)
|
methods.append(MFAType.OTP)
|
||||||
if self.phone:
|
if settings.XPACK_ENABLED and settings.SMS_ENABLED and self.phone:
|
||||||
methods.append(MFAType.SMS_CODE)
|
methods.append(MFAType.SMS_CODE)
|
||||||
return methods
|
return methods
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# common
|
# common
|
||||||
gcc
|
gcc
|
||||||
|
g++
|
||||||
cmake
|
cmake
|
||||||
curl
|
curl
|
||||||
wget
|
wget
|
||||||
@@ -12,6 +13,7 @@ default-mysql-client
|
|||||||
default-libmysqlclient-dev
|
default-libmysqlclient-dev
|
||||||
|
|
||||||
# Pillow
|
# Pillow
|
||||||
|
libpq-dev
|
||||||
libffi-dev
|
libffi-dev
|
||||||
# libfreetype6-dev
|
# libfreetype6-dev
|
||||||
# libfribidi-dev
|
# libfribidi-dev
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite libkrb5-dev sshpass libmysqlclient-dev
|
g++ libpq-dev libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite libkrb5-dev sshpass libmysqlclient-dev
|
||||||
|
|||||||
Reference in New Issue
Block a user