mirror of https://github.com/jumpserver/jumpserver
feat(ticket): 调整申请资产工单
parent
b331730422
commit
f8e248f0af
|
@ -1,11 +1,21 @@
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
|
|
||||||
from ..mixins.api import SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin
|
from ..mixins.api import (
|
||||||
|
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class JmsGenericViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, GenericViewSet):
|
class JmsGenericViewSet(SerializerMixin2,
|
||||||
|
QuerySetMixin,
|
||||||
|
ExtraFilterFieldsMixin,
|
||||||
|
PaginatedResponseMixin,
|
||||||
|
GenericViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JMSModelViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, ModelViewSet):
|
class JMSModelViewSet(SerializerMixin2,
|
||||||
|
QuerySetMixin,
|
||||||
|
ExtraFilterFieldsMixin,
|
||||||
|
PaginatedResponseMixin,
|
||||||
|
ModelViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -67,6 +67,17 @@ class ExtraFilterFieldsMixin:
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedResponseMixin:
|
||||||
|
def get_paginated_response_with_query_set(self, queryset):
|
||||||
|
page = self.paginate_queryset(queryset)
|
||||||
|
if page is not None:
|
||||||
|
serializer = self.get_serializer(page, many=True)
|
||||||
|
return self.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin):
|
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ import time
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
|
from .timezone import dt_formater
|
||||||
|
|
||||||
|
|
||||||
UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}')
|
UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}')
|
||||||
ipip_db = None
|
ipip_db = None
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
from django.utils import timezone as dj_timezone
|
||||||
|
from rest_framework.fields import DateTimeField
|
||||||
|
|
||||||
|
max = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
def astimezone(dt: datetime.datetime, tzinfo: pytz.tzinfo.DstTzInfo):
|
||||||
|
assert dj_timezone.is_aware(dt)
|
||||||
|
return tzinfo.normalize(dt.astimezone(tzinfo))
|
||||||
|
|
||||||
|
|
||||||
|
def as_china_cst(dt: datetime.datetime):
|
||||||
|
return astimezone(dt, pytz.timezone('Asia/Shanghai'))
|
||||||
|
|
||||||
|
|
||||||
|
def as_current_tz(dt: datetime.datetime):
|
||||||
|
return astimezone(dt, dj_timezone.get_current_timezone())
|
||||||
|
|
||||||
|
|
||||||
|
def utcnow():
|
||||||
|
return dj_timezone.now()
|
||||||
|
|
||||||
|
|
||||||
|
def now():
|
||||||
|
return as_current_tz(utcnow())
|
||||||
|
|
||||||
|
|
||||||
|
_rest_dt_field = DateTimeField()
|
||||||
|
dt_parser = _rest_dt_field.to_internal_value
|
||||||
|
dt_formater = _rest_dt_field.to_representation
|
|
@ -96,3 +96,5 @@ XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID
|
||||||
LOGO_URLS = DYNAMIC.LOGO_URLS
|
LOGO_URLS = DYNAMIC.LOGO_URLS
|
||||||
|
|
||||||
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED
|
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED
|
||||||
|
|
||||||
|
DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,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: 2020-07-27 19:01+0800\n"
|
"POT-Creation-Date: 2020-07-28 11:25+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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"
|
||||||
|
@ -28,7 +28,7 @@ msgstr "自定义"
|
||||||
#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:22
|
#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:22
|
||||||
#: perms/models/base.py:48 settings/models.py:27 terminal/models.py:26
|
#: perms/models/base.py:48 settings/models.py:27 terminal/models.py:26
|
||||||
#: terminal/models.py:342 terminal/models.py:374 terminal/models.py:411
|
#: terminal/models.py:342 terminal/models.py:374 terminal/models.py:411
|
||||||
#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:473
|
#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:489
|
||||||
#: users/templates/users/_select_user_modal.html:13
|
#: users/templates/users/_select_user_modal.html:13
|
||||||
#: users/templates/users/user_asset_permission.html:37
|
#: users/templates/users/user_asset_permission.html:37
|
||||||
#: users/templates/users/user_asset_permission.html:154
|
#: users/templates/users/user_asset_permission.html:154
|
||||||
|
@ -47,7 +47,7 @@ msgid "Name"
|
||||||
msgstr "名称"
|
msgstr "名称"
|
||||||
|
|
||||||
#: applications/models/database_app.py:22 assets/models/cmd_filter.py:52
|
#: applications/models/database_app.py:22 assets/models/cmd_filter.py:52
|
||||||
#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:45
|
#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:46
|
||||||
#: users/templates/users/user_granted_database_app.html:35
|
#: users/templates/users/user_granted_database_app.html:35
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "类型"
|
msgstr "类型"
|
||||||
|
@ -77,7 +77,7 @@ msgstr "数据库"
|
||||||
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
|
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
|
||||||
#: orgs/models.py:25 perms/models/base.py:56 settings/models.py:32
|
#: orgs/models.py:25 perms/models/base.py:56 settings/models.py:32
|
||||||
#: terminal/models.py:36 terminal/models.py:381 terminal/models.py:418
|
#: terminal/models.py:36 terminal/models.py:381 terminal/models.py:418
|
||||||
#: users/models/group.py:16 users/models/user.py:506
|
#: users/models/group.py:16 users/models/user.py:522
|
||||||
#: users/templates/users/user_detail.html:115
|
#: users/templates/users/user_detail.html:115
|
||||||
#: users/templates/users/user_granted_database_app.html:38
|
#: users/templates/users/user_granted_database_app.html:38
|
||||||
#: users/templates/users/user_granted_remote_app.html:37
|
#: users/templates/users/user_granted_remote_app.html:37
|
||||||
|
@ -132,7 +132,7 @@ msgstr "参数"
|
||||||
#: assets/models/base.py:240 assets/models/cluster.py:28
|
#: assets/models/base.py:240 assets/models/cluster.py:28
|
||||||
#: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60
|
#: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60
|
||||||
#: assets/models/group.py:21 common/mixins/models.py:49 orgs/models.py:23
|
#: assets/models/group.py:21 common/mixins/models.py:49 orgs/models.py:23
|
||||||
#: orgs/models.py:316 perms/models/base.py:54 users/models/user.py:514
|
#: orgs/models.py:316 perms/models/base.py:54 users/models/user.py:530
|
||||||
#: users/serializers/group.py:35 users/templates/users/user_detail.html:97
|
#: users/serializers/group.py:35 users/templates/users/user_detail.html:97
|
||||||
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56
|
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56
|
||||||
#: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30
|
#: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30
|
||||||
|
@ -189,7 +189,7 @@ msgstr "基础"
|
||||||
msgid "Charset"
|
msgid "Charset"
|
||||||
msgstr "编码"
|
msgstr "编码"
|
||||||
|
|
||||||
#: assets/models/asset.py:148 tickets/models/ticket.py:40
|
#: assets/models/asset.py:148 tickets/models/ticket.py:41
|
||||||
msgid "Meta"
|
msgid "Meta"
|
||||||
msgstr "元数据"
|
msgstr "元数据"
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ msgstr "IP"
|
||||||
|
|
||||||
#: assets/models/asset.py:187 assets/serializers/asset_user.py:45
|
#: assets/models/asset.py:187 assets/serializers/asset_user.py:45
|
||||||
#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:51
|
#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:51
|
||||||
#: tickets/serializers/request_asset_perm.py:14
|
#: tickets/serializers/request_asset_perm.py:21
|
||||||
#: users/templates/users/_granted_assets.html:25
|
#: users/templates/users/_granted_assets.html:25
|
||||||
#: users/templates/users/user_asset_permission.html:157
|
#: users/templates/users/user_asset_permission.html:157
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
|
@ -339,7 +339,7 @@ msgstr ""
|
||||||
#: audits/models.py:99 authentication/forms.py:11
|
#: audits/models.py:99 authentication/forms.py:11
|
||||||
#: authentication/templates/authentication/login.html:21
|
#: authentication/templates/authentication/login.html:21
|
||||||
#: authentication/templates/authentication/xpack_login.html:101
|
#: authentication/templates/authentication/xpack_login.html:101
|
||||||
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:471
|
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:487
|
||||||
#: users/templates/users/_select_user_modal.html:14
|
#: users/templates/users/_select_user_modal.html:14
|
||||||
#: users/templates/users/user_detail.html:53
|
#: users/templates/users/user_detail.html:53
|
||||||
#: users/templates/users/user_list.html:15
|
#: users/templates/users/user_list.html:15
|
||||||
|
@ -391,7 +391,7 @@ msgstr "带宽"
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr "联系人"
|
msgstr "联系人"
|
||||||
|
|
||||||
#: assets/models/cluster.py:22 users/models/user.py:492
|
#: assets/models/cluster.py:22 users/models/user.py:508
|
||||||
#: users/templates/users/user_detail.html:62
|
#: users/templates/users/user_detail.html:62
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "手机"
|
msgstr "手机"
|
||||||
|
@ -417,7 +417,7 @@ msgid "Default"
|
||||||
msgstr "默认"
|
msgstr "默认"
|
||||||
|
|
||||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||||
#: users/models/user.py:635
|
#: users/models/user.py:655
|
||||||
msgid "System"
|
msgid "System"
|
||||||
msgstr "系统"
|
msgstr "系统"
|
||||||
|
|
||||||
|
@ -485,7 +485,7 @@ msgstr "每行一个命令"
|
||||||
#: assets/models/cmd_filter.py:56 audits/models.py:57
|
#: assets/models/cmd_filter.py:56 audits/models.py:57
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:34
|
#: authentication/templates/authentication/_access_key_modal.html:34
|
||||||
#: perms/forms/asset_permission.py:20
|
#: perms/forms/asset_permission.py:20
|
||||||
#: tickets/serializers/request_asset_perm.py:54
|
#: tickets/serializers/request_asset_perm.py:60
|
||||||
#: tickets/serializers/ticket.py:26
|
#: tickets/serializers/ticket.py:26
|
||||||
#: users/templates/users/_granted_assets.html:29
|
#: users/templates/users/_granted_assets.html:29
|
||||||
#: users/templates/users/user_asset_permission.html:44
|
#: users/templates/users/user_asset_permission.html:44
|
||||||
|
@ -540,10 +540,10 @@ msgstr "默认资产组"
|
||||||
#: perms/forms/remote_app_permission.py:40 perms/models/base.py:49
|
#: perms/forms/remote_app_permission.py:40 perms/models/base.py:49
|
||||||
#: templates/index.html:78 terminal/backends/command/models.py:18
|
#: templates/index.html:78 terminal/backends/command/models.py:18
|
||||||
#: terminal/backends/command/serializers.py:12 terminal/models.py:185
|
#: terminal/backends/command/serializers.py:12 terminal/models.py:185
|
||||||
#: tickets/models/ticket.py:35 tickets/models/ticket.py:130
|
#: tickets/models/ticket.py:36 tickets/models/ticket.py:135
|
||||||
#: tickets/serializers/request_asset_perm.py:55
|
#: tickets/serializers/request_asset_perm.py:61
|
||||||
#: tickets/serializers/ticket.py:27 users/forms/group.py:15
|
#: tickets/serializers/ticket.py:27 users/forms/group.py:15
|
||||||
#: users/models/user.py:157 users/models/user.py:623
|
#: users/models/user.py:157 users/models/user.py:643
|
||||||
#: users/serializers/group.py:20
|
#: users/serializers/group.py:20
|
||||||
#: users/templates/users/user_asset_permission.html:38
|
#: users/templates/users/user_asset_permission.html:38
|
||||||
#: users/templates/users/user_asset_permission.html:64
|
#: users/templates/users/user_asset_permission.html:64
|
||||||
|
@ -649,7 +649,7 @@ msgstr "SFTP根路径"
|
||||||
#: perms/models/remote_app_permission.py:16 templates/_nav.html:45
|
#: perms/models/remote_app_permission.py:16 templates/_nav.html:45
|
||||||
#: terminal/backends/command/models.py:20
|
#: terminal/backends/command/models.py:20
|
||||||
#: terminal/backends/command/serializers.py:14 terminal/models.py:189
|
#: terminal/backends/command/serializers.py:14 terminal/models.py:189
|
||||||
#: tickets/serializers/request_asset_perm.py:16
|
#: tickets/serializers/request_asset_perm.py:23
|
||||||
#: users/templates/users/_granted_assets.html:27
|
#: users/templates/users/_granted_assets.html:27
|
||||||
#: users/templates/users/user_asset_permission.html:42
|
#: users/templates/users/user_asset_permission.html:42
|
||||||
#: users/templates/users/user_asset_permission.html:76
|
#: users/templates/users/user_asset_permission.html:76
|
||||||
|
@ -708,14 +708,14 @@ msgid "Backend"
|
||||||
msgstr "后端"
|
msgstr "后端"
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:75 users/forms/profile.py:148
|
#: assets/serializers/asset_user.py:75 users/forms/profile.py:148
|
||||||
#: users/models/user.py:503 users/templates/users/user_password_update.html:48
|
#: users/models/user.py:519 users/templates/users/user_password_update.html:48
|
||||||
#: users/templates/users/user_profile.html:69
|
#: users/templates/users/user_profile.html:69
|
||||||
#: users/templates/users/user_profile_update.html:46
|
#: users/templates/users/user_profile_update.html:46
|
||||||
#: users/templates/users/user_pubkey_update.html:46
|
#: users/templates/users/user_pubkey_update.html:46
|
||||||
msgid "Public key"
|
msgid "Public key"
|
||||||
msgstr "SSH公钥"
|
msgstr "SSH公钥"
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:79 users/models/user.py:500
|
#: assets/serializers/asset_user.py:79 users/models/user.py:516
|
||||||
msgid "Private key"
|
msgid "Private key"
|
||||||
msgstr "ssh私钥"
|
msgstr "ssh私钥"
|
||||||
|
|
||||||
|
@ -875,7 +875,7 @@ msgstr "没有匹配到资产,结束任务"
|
||||||
#: users/templates/users/user_list.html:98
|
#: users/templates/users/user_list.html:98
|
||||||
#: users/templates/users/user_remote_app_permission.html:111
|
#: users/templates/users/user_remote_app_permission.html:111
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "删除"
|
msgstr "删除文件"
|
||||||
|
|
||||||
#: audits/models.py:27
|
#: audits/models.py:27
|
||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
|
@ -920,7 +920,7 @@ msgid "Success"
|
||||||
msgstr "成功"
|
msgstr "成功"
|
||||||
|
|
||||||
#: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52
|
#: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52
|
||||||
#: terminal/models.py:199 tickets/serializers/request_asset_perm.py:18
|
#: terminal/models.py:199 tickets/serializers/request_asset_perm.py:25
|
||||||
#: xpack/plugins/change_auth_plan/models.py:177
|
#: xpack/plugins/change_auth_plan/models.py:177
|
||||||
#: xpack/plugins/change_auth_plan/models.py:308
|
#: xpack/plugins/change_auth_plan/models.py:308
|
||||||
#: xpack/plugins/gathered_user/models.py:76
|
#: xpack/plugins/gathered_user/models.py:76
|
||||||
|
@ -1000,8 +1000,8 @@ msgstr "Agent"
|
||||||
#: audits/models.py:104
|
#: audits/models.py:104
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
||||||
#: authentication/templates/authentication/login_otp.html:6
|
#: authentication/templates/authentication/login_otp.html:6
|
||||||
#: users/forms/profile.py:52 users/models/user.py:495
|
#: users/forms/profile.py:52 users/models/user.py:511
|
||||||
#: users/serializers/user.py:224 users/templates/users/user_detail.html:77
|
#: users/serializers/user.py:234 users/templates/users/user_detail.html:77
|
||||||
#: users/templates/users/user_profile.html:87
|
#: users/templates/users/user_profile.html:87
|
||||||
msgid "MFA"
|
msgid "MFA"
|
||||||
msgstr "多因子认证"
|
msgstr "多因子认证"
|
||||||
|
@ -1011,7 +1011,7 @@ msgstr "多因子认证"
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
msgstr "原因"
|
msgstr "原因"
|
||||||
|
|
||||||
#: audits/models.py:106 tickets/serializers/request_asset_perm.py:53
|
#: audits/models.py:106 tickets/serializers/request_asset_perm.py:59
|
||||||
#: tickets/serializers/ticket.py:25 xpack/plugins/cloud/models.py:211
|
#: tickets/serializers/ticket.py:25 xpack/plugins/cloud/models.py:211
|
||||||
#: xpack/plugins/cloud/models.py:269
|
#: xpack/plugins/cloud/models.py:269
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
|
@ -1193,7 +1193,7 @@ msgstr "SSH密钥"
|
||||||
msgid "Reviewers"
|
msgid "Reviewers"
|
||||||
msgstr "审批人"
|
msgstr "审批人"
|
||||||
|
|
||||||
#: authentication/models.py:53 tickets/models/ticket.py:26
|
#: authentication/models.py:53 tickets/models/ticket.py:27
|
||||||
#: users/templates/users/user_detail.html:250
|
#: users/templates/users/user_detail.html:250
|
||||||
msgid "Login confirm"
|
msgid "Login confirm"
|
||||||
msgstr "登录复核"
|
msgstr "登录复核"
|
||||||
|
@ -1228,7 +1228,7 @@ msgid "Show"
|
||||||
msgstr "显示"
|
msgstr "显示"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:66
|
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||||
#: users/models/user.py:393 users/serializers/user.py:221
|
#: users/models/user.py:409 users/serializers/user.py:231
|
||||||
#: users/templates/users/user_profile.html:94
|
#: users/templates/users/user_profile.html:94
|
||||||
#: users/templates/users/user_profile.html:163
|
#: users/templates/users/user_profile.html:163
|
||||||
#: users/templates/users/user_profile.html:166
|
#: users/templates/users/user_profile.html:166
|
||||||
|
@ -1237,7 +1237,7 @@ msgid "Disable"
|
||||||
msgstr "禁用"
|
msgstr "禁用"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:67
|
#: authentication/templates/authentication/_access_key_modal.html:67
|
||||||
#: users/models/user.py:394 users/serializers/user.py:222
|
#: users/models/user.py:410 users/serializers/user.py:232
|
||||||
#: users/templates/users/user_profile.html:92
|
#: users/templates/users/user_profile.html:92
|
||||||
#: users/templates/users/user_profile.html:170
|
#: users/templates/users/user_profile.html:170
|
||||||
msgid "Enable"
|
msgid "Enable"
|
||||||
|
@ -1249,7 +1249,7 @@ msgstr "删除成功"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:155
|
#: authentication/templates/authentication/_access_key_modal.html:155
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
|
||||||
#: templates/_modal.html:22 tickets/models/ticket.py:70
|
#: templates/_modal.html:22 tickets/models/ticket.py:73
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "关闭"
|
msgstr "关闭"
|
||||||
|
|
||||||
|
@ -1628,11 +1628,11 @@ msgstr "磁盘使用率超过 80%: {} => {}"
|
||||||
|
|
||||||
#: orgs/api.py:54
|
#: orgs/api.py:54
|
||||||
msgid "Organization contains undeleted resources"
|
msgid "Organization contains undeleted resources"
|
||||||
msgstr "组织内包含未删除的资源"
|
msgstr ""
|
||||||
|
|
||||||
#: orgs/api.py:58
|
#: orgs/api.py:58
|
||||||
msgid "The current organization cannot be deleted"
|
msgid "The current organization cannot be deleted"
|
||||||
msgstr "当能删除当前所在组织"
|
msgstr ""
|
||||||
|
|
||||||
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:40
|
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:40
|
||||||
#: orgs/models.py:311
|
#: orgs/models.py:311
|
||||||
|
@ -1647,7 +1647,7 @@ msgstr "组织管理员"
|
||||||
msgid "Organization auditor"
|
msgid "Organization auditor"
|
||||||
msgstr "组织审计员"
|
msgstr "组织审计员"
|
||||||
|
|
||||||
#: orgs/models.py:313 users/forms/user.py:27 users/models/user.py:483
|
#: orgs/models.py:313 users/forms/user.py:27 users/models/user.py:499
|
||||||
#: users/templates/users/_select_user_modal.html:15
|
#: users/templates/users/_select_user_modal.html:15
|
||||||
#: users/templates/users/user_detail.html:73
|
#: users/templates/users/user_detail.html:73
|
||||||
#: users/templates/users/user_list.html:16
|
#: users/templates/users/user_list.html:16
|
||||||
|
@ -1672,7 +1672,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
|
||||||
#: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41
|
#: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41
|
||||||
#: perms/forms/remote_app_permission.py:43 perms/models/base.py:50
|
#: perms/forms/remote_app_permission.py:43 perms/models/base.py:50
|
||||||
#: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31
|
#: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31
|
||||||
#: users/models/user.py:479 users/serializers/user.py:43
|
#: users/models/user.py:495 users/serializers/user.py:48
|
||||||
#: users/templates/users/_select_user_modal.html:16
|
#: users/templates/users/_select_user_modal.html:16
|
||||||
#: users/templates/users/user_asset_permission.html:39
|
#: users/templates/users/user_asset_permission.html:39
|
||||||
#: users/templates/users/user_asset_permission.html:67
|
#: users/templates/users/user_asset_permission.html:67
|
||||||
|
@ -1719,15 +1719,15 @@ msgstr "上传下载"
|
||||||
|
|
||||||
#: perms/models/asset_permission.py:40
|
#: perms/models/asset_permission.py:40
|
||||||
msgid "Clipboard copy"
|
msgid "Clipboard copy"
|
||||||
msgstr "剪切板复制"
|
msgstr ""
|
||||||
|
|
||||||
#: perms/models/asset_permission.py:41
|
#: perms/models/asset_permission.py:41
|
||||||
msgid "Clipboard paste"
|
msgid "Clipboard paste"
|
||||||
msgstr "剪切板粘贴"
|
msgstr ""
|
||||||
|
|
||||||
#: perms/models/asset_permission.py:42
|
#: perms/models/asset_permission.py:42
|
||||||
msgid "Clipboard copy paste"
|
msgid "Clipboard copy paste"
|
||||||
msgstr "剪切板复制粘贴"
|
msgstr ""
|
||||||
|
|
||||||
#: perms/models/asset_permission.py:93
|
#: perms/models/asset_permission.py:93
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
|
@ -1738,8 +1738,8 @@ msgstr "动作"
|
||||||
msgid "Asset permission"
|
msgid "Asset permission"
|
||||||
msgstr "资产授权"
|
msgstr "资产授权"
|
||||||
|
|
||||||
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:20
|
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:27
|
||||||
#: users/models/user.py:511 users/templates/users/user_detail.html:93
|
#: users/models/user.py:527 users/templates/users/user_detail.html:93
|
||||||
#: users/templates/users/user_profile.html:120
|
#: users/templates/users/user_profile.html:120
|
||||||
msgid "Date expired"
|
msgid "Date expired"
|
||||||
msgstr "失效日期"
|
msgstr "失效日期"
|
||||||
|
@ -2479,124 +2479,151 @@ msgstr "结束日期"
|
||||||
msgid "Args"
|
msgid "Args"
|
||||||
msgstr "参数"
|
msgstr "参数"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:41
|
#: tickets/api/request_asset_perm.py:43
|
||||||
msgid "Ticket closed"
|
msgid "Ticket closed"
|
||||||
msgstr "工单已关闭"
|
msgstr "工单已关闭"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:44
|
#: tickets/api/request_asset_perm.py:46
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Ticket has %s"
|
msgid "Ticket has %s"
|
||||||
msgstr "工单已%s"
|
msgstr "工单已%s"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:69
|
#: tickets/api/request_asset_perm.py:93
|
||||||
msgid "Superuser"
|
|
||||||
msgstr "超级管理员"
|
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:99
|
|
||||||
msgid "Confirm assets first"
|
msgid "Confirm assets first"
|
||||||
msgstr "请先确认资产"
|
msgstr "请先确认资产"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:102
|
#: tickets/api/request_asset_perm.py:96
|
||||||
msgid "Confirmed assets changed"
|
msgid "Confirmed assets changed"
|
||||||
msgstr "确认的资产变更了"
|
msgstr "确认的资产变更了"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:106
|
#: tickets/api/request_asset_perm.py:100
|
||||||
msgid "Confirm system-user first"
|
msgid "Confirm system-user first"
|
||||||
msgstr "请先确认系统用户"
|
msgstr "请先确认系统用户"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:110
|
#: tickets/api/request_asset_perm.py:104
|
||||||
msgid "Confirmed system-user changed"
|
msgid "Confirmed system-user changed"
|
||||||
msgstr "确认的系统用户变更了"
|
msgstr "确认的系统用户变更了"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:113 xpack/plugins/cloud/models.py:202
|
#: tickets/api/request_asset_perm.py:107 xpack/plugins/cloud/models.py:202
|
||||||
msgid "Succeed"
|
msgid "Succeed"
|
||||||
msgstr "成功"
|
msgstr "成功"
|
||||||
|
|
||||||
#: tickets/api/request_asset_perm.py:121
|
#: tickets/api/request_asset_perm.py:114
|
||||||
|
msgid "From request ticket: {} {}"
|
||||||
|
msgstr "来自工单申请: {} {}"
|
||||||
|
|
||||||
|
#: tickets/api/request_asset_perm.py:116
|
||||||
msgid "{} request assets, approved by {}"
|
msgid "{} request assets, approved by {}"
|
||||||
msgstr "{} 申请资产,通过人 {}"
|
msgstr "{} 申请资产,通过人 {}"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:18 tickets/models/ticket.py:72
|
#: tickets/models/ticket.py:19 tickets/models/ticket.py:75
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "开启"
|
msgstr "开启"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:19
|
#: tickets/models/ticket.py:20
|
||||||
msgid "Closed"
|
msgid "Closed"
|
||||||
msgstr "关闭"
|
msgstr "关闭"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:25
|
#: tickets/models/ticket.py:26
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "一般"
|
msgstr "一般"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:27
|
#: tickets/models/ticket.py:28
|
||||||
msgid "Request asset permission"
|
msgid "Request asset permission"
|
||||||
msgstr "申请资产权限"
|
msgstr "申请资产权限"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:32
|
#: tickets/models/ticket.py:33
|
||||||
msgid "Approve"
|
msgid "Approve"
|
||||||
msgstr "同意"
|
msgstr "同意"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:33
|
#: tickets/models/ticket.py:34
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr "拒绝"
|
msgstr "拒绝"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:36 tickets/models/ticket.py:131
|
#: tickets/models/ticket.py:37 tickets/models/ticket.py:136
|
||||||
msgid "User display name"
|
msgid "User display name"
|
||||||
msgstr "用户显示名称"
|
msgstr "用户显示名称"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:38
|
#: tickets/models/ticket.py:39
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "标题"
|
msgstr "标题"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:39 tickets/models/ticket.py:132
|
#: tickets/models/ticket.py:40 tickets/models/ticket.py:137
|
||||||
msgid "Body"
|
msgid "Body"
|
||||||
msgstr "内容"
|
msgstr "内容"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:41
|
#: tickets/models/ticket.py:42
|
||||||
msgid "Assignee"
|
msgid "Assignee"
|
||||||
msgstr "处理人"
|
msgstr "处理人"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:42
|
#: tickets/models/ticket.py:43
|
||||||
msgid "Assignee display name"
|
msgid "Assignee display name"
|
||||||
msgstr "处理人名称"
|
msgstr "处理人名称"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:43
|
#: tickets/models/ticket.py:44
|
||||||
msgid "Assignees"
|
msgid "Assignees"
|
||||||
msgstr "待处理人"
|
msgstr "待处理人"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:44
|
#: tickets/models/ticket.py:45
|
||||||
msgid "Assignees display name"
|
msgid "Assignees display name"
|
||||||
msgstr "待处理人名称"
|
msgstr "待处理人名称"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:73
|
#: tickets/models/ticket.py:76
|
||||||
msgid "{} {} this ticket"
|
msgid "{} {} this ticket"
|
||||||
msgstr "{} {} 这个工单"
|
msgstr "{} {} 这个工单"
|
||||||
|
|
||||||
#: tickets/models/ticket.py:84
|
#: tickets/models/ticket.py:87
|
||||||
msgid "this ticket"
|
msgid "this ticket"
|
||||||
msgstr "这个工单"
|
msgstr "这个工单"
|
||||||
|
|
||||||
#: tickets/serializers/request_asset_perm.py:12
|
#: tickets/serializers/request_asset_perm.py:19
|
||||||
msgid "IP group"
|
msgid "IP group"
|
||||||
msgstr "IP组"
|
msgstr "IP组"
|
||||||
|
|
||||||
#: tickets/serializers/request_asset_perm.py:24
|
#: tickets/serializers/request_asset_perm.py:31
|
||||||
msgid "Confirmed assets"
|
msgid "Confirmed assets"
|
||||||
msgstr "确认的资产"
|
msgstr "确认的资产"
|
||||||
|
|
||||||
#: tickets/serializers/request_asset_perm.py:28
|
#: tickets/serializers/request_asset_perm.py:34
|
||||||
msgid "Confirmed system user"
|
msgid "Confirmed system user"
|
||||||
msgstr "确认的系统用户"
|
msgstr "确认的系统用户"
|
||||||
|
|
||||||
#: tickets/serializers/request_asset_perm.py:65
|
#: tickets/serializers/request_asset_perm.py:83
|
||||||
msgid "Must be organization admin or superuser"
|
msgid "Invalid `org_id`"
|
||||||
msgstr "必须是组织管理员或者超级管理员"
|
msgstr "无效的 `org_id`"
|
||||||
|
|
||||||
#: tickets/utils.py:18
|
#: tickets/serializers/request_asset_perm.py:93
|
||||||
|
msgid "Field `assignees` must be organization admin or superuser"
|
||||||
|
msgstr "字段 assignees 必须是组织管理员或者超级管理员"
|
||||||
|
|
||||||
|
#: tickets/serializers/request_asset_perm.py:143
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" Type: {type}<br>\n"
|
||||||
|
" User: {username}<br>\n"
|
||||||
|
" Ip group: {ips}<br>\n"
|
||||||
|
" Hostname: {hostname}<br>\n"
|
||||||
|
" System user: {system_user}<br>\n"
|
||||||
|
" Date start: {date_start}<br>\n"
|
||||||
|
" Date expired: {date_expired}<br>\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
" 类型: {type}<br>\n"
|
||||||
|
" 用户: {username}<br>\n"
|
||||||
|
" IP 组: {ips}<br>\n"
|
||||||
|
" 主机名: {hostname}<br>\n"
|
||||||
|
" 系统用户: {system_user}<br>\n"
|
||||||
|
" 开始时间: {date_start}<br>\n"
|
||||||
|
" 过期时间: {date_expired}<br>\n"
|
||||||
|
" "
|
||||||
|
|
||||||
|
#: tickets/utils.py:20
|
||||||
msgid "New ticket"
|
msgid "New ticket"
|
||||||
msgstr "新工单"
|
msgstr "新工单"
|
||||||
|
|
||||||
#: tickets/utils.py:21
|
#: tickets/utils.py:28
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -2621,11 +2648,11 @@ msgstr ""
|
||||||
" </div>\n"
|
" </div>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: tickets/utils.py:40
|
#: tickets/utils.py:47
|
||||||
msgid "Ticket has been reply"
|
msgid "Ticket has been reply"
|
||||||
msgstr "工单已被回复"
|
msgstr "工单已被回复"
|
||||||
|
|
||||||
#: tickets/utils.py:41
|
#: tickets/utils.py:48
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -2656,7 +2683,7 @@ msgstr ""
|
||||||
" </div>\n"
|
" </div>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: users/api/user.py:126
|
#: users/api/user.py:147
|
||||||
msgid "Could not reset self otp, use profile reset instead"
|
msgid "Could not reset self otp, use profile reset instead"
|
||||||
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
|
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
|
||||||
|
|
||||||
|
@ -2702,7 +2729,7 @@ msgstr "确认密码"
|
||||||
msgid "Password does not match"
|
msgid "Password does not match"
|
||||||
msgstr "密码不一致"
|
msgstr "密码不一致"
|
||||||
|
|
||||||
#: users/forms/profile.py:89 users/models/user.py:475
|
#: users/forms/profile.py:89 users/models/user.py:491
|
||||||
#: users/templates/users/user_detail.html:57
|
#: users/templates/users/user_detail.html:57
|
||||||
#: users/templates/users/user_profile.html:59
|
#: users/templates/users/user_profile.html:59
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
|
@ -2738,12 +2765,12 @@ msgid "Public key should not be the same as your old one."
|
||||||
msgstr "不能和原来的密钥相同"
|
msgstr "不能和原来的密钥相同"
|
||||||
|
|
||||||
#: users/forms/profile.py:137 users/forms/user.py:90
|
#: users/forms/profile.py:137 users/forms/user.py:90
|
||||||
#: users/serializers/user.py:185 users/serializers/user.py:266
|
#: users/serializers/user.py:194 users/serializers/user.py:276
|
||||||
#: users/serializers/user.py:324
|
#: users/serializers/user.py:334
|
||||||
msgid "Not a valid ssh public key"
|
msgid "Not a valid ssh public key"
|
||||||
msgstr "SSH密钥不合法"
|
msgstr "SSH密钥不合法"
|
||||||
|
|
||||||
#: users/forms/user.py:31 users/models/user.py:518
|
#: users/forms/user.py:31 users/models/user.py:534
|
||||||
#: users/templates/users/user_detail.html:89
|
#: users/templates/users/user_detail.html:89
|
||||||
#: users/templates/users/user_list.html:18
|
#: users/templates/users/user_list.html:18
|
||||||
#: users/templates/users/user_profile.html:102
|
#: users/templates/users/user_profile.html:102
|
||||||
|
@ -2763,15 +2790,15 @@ msgstr "添加到用户组"
|
||||||
msgid "* Your password does not meet the requirements"
|
msgid "* Your password does not meet the requirements"
|
||||||
msgstr "* 您的密码不符合要求"
|
msgstr "* 您的密码不符合要求"
|
||||||
|
|
||||||
#: users/forms/user.py:124 users/serializers/user.py:31
|
#: users/forms/user.py:124 users/serializers/user.py:36
|
||||||
msgid "Reset link will be generated and sent to the user"
|
msgid "Reset link will be generated and sent to the user"
|
||||||
msgstr "生成重置密码链接,通过邮件发送给用户"
|
msgstr "生成重置密码链接,通过邮件发送给用户"
|
||||||
|
|
||||||
#: users/forms/user.py:125 users/serializers/user.py:32
|
#: users/forms/user.py:125 users/serializers/user.py:37
|
||||||
msgid "Set password"
|
msgid "Set password"
|
||||||
msgstr "设置密码"
|
msgstr "设置密码"
|
||||||
|
|
||||||
#: users/forms/user.py:132 users/serializers/user.py:39
|
#: users/forms/user.py:132 users/serializers/user.py:44
|
||||||
#: xpack/plugins/change_auth_plan/models.py:61
|
#: xpack/plugins/change_auth_plan/models.py:61
|
||||||
#: xpack/plugins/change_auth_plan/serializers.py:30
|
#: xpack/plugins/change_auth_plan/serializers.py:30
|
||||||
msgid "Password strategy"
|
msgid "Password strategy"
|
||||||
|
@ -2789,79 +2816,79 @@ msgstr "超级审计员"
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr "应用程序"
|
msgstr "应用程序"
|
||||||
|
|
||||||
#: users/models/user.py:395 users/templates/users/user_profile.html:90
|
#: users/models/user.py:411 users/templates/users/user_profile.html:90
|
||||||
msgid "Force enable"
|
msgid "Force enable"
|
||||||
msgstr "强制启用"
|
msgstr "强制启用"
|
||||||
|
|
||||||
#: users/models/user.py:462
|
#: users/models/user.py:478
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "数据库"
|
msgstr "数据库"
|
||||||
|
|
||||||
#: users/models/user.py:486
|
#: users/models/user.py:502
|
||||||
msgid "Avatar"
|
msgid "Avatar"
|
||||||
msgstr "头像"
|
msgstr "头像"
|
||||||
|
|
||||||
#: users/models/user.py:489 users/templates/users/user_detail.html:68
|
#: users/models/user.py:505 users/templates/users/user_detail.html:68
|
||||||
msgid "Wechat"
|
msgid "Wechat"
|
||||||
msgstr "微信"
|
msgstr "微信"
|
||||||
|
|
||||||
#: users/models/user.py:522
|
#: users/models/user.py:538
|
||||||
msgid "Date password last updated"
|
msgid "Date password last updated"
|
||||||
msgstr "最后更新密码日期"
|
msgstr "最后更新密码日期"
|
||||||
|
|
||||||
#: users/models/user.py:631
|
#: users/models/user.py:651
|
||||||
msgid "Administrator"
|
msgid "Administrator"
|
||||||
msgstr "管理员"
|
msgstr "管理员"
|
||||||
|
|
||||||
#: users/models/user.py:634
|
#: users/models/user.py:654
|
||||||
msgid "Administrator is the super user of system"
|
msgid "Administrator is the super user of system"
|
||||||
msgstr "Administrator是初始的超级管理员"
|
msgstr "Administrator是初始的超级管理员"
|
||||||
|
|
||||||
#: users/serializers/user.py:72 users/serializers/user.py:237
|
#: users/serializers/user.py:54 users/serializers/user.py:90
|
||||||
msgid "Is first login"
|
|
||||||
msgstr "首次登录"
|
|
||||||
|
|
||||||
#: users/serializers/user.py:73
|
|
||||||
msgid "Is valid"
|
|
||||||
msgstr "账户是否有效"
|
|
||||||
|
|
||||||
#: users/serializers/user.py:74
|
|
||||||
msgid "Is expired"
|
|
||||||
msgstr " 是否过期"
|
|
||||||
|
|
||||||
#: users/serializers/user.py:75
|
|
||||||
msgid "Avatar url"
|
|
||||||
msgstr "头像路径"
|
|
||||||
|
|
||||||
#: users/serializers/user.py:79
|
|
||||||
msgid "Groups name"
|
|
||||||
msgstr "用户组名"
|
|
||||||
|
|
||||||
#: users/serializers/user.py:80
|
|
||||||
msgid "Source name"
|
|
||||||
msgstr "用户来源名"
|
|
||||||
|
|
||||||
#: users/serializers/user.py:81
|
|
||||||
msgid "Organization role name"
|
msgid "Organization role name"
|
||||||
msgstr "组织角色名称"
|
msgstr "组织角色名称"
|
||||||
|
|
||||||
|
#: users/serializers/user.py:81 users/serializers/user.py:247
|
||||||
|
msgid "Is first login"
|
||||||
|
msgstr "首次登录"
|
||||||
|
|
||||||
#: users/serializers/user.py:82
|
#: users/serializers/user.py:82
|
||||||
|
msgid "Is valid"
|
||||||
|
msgstr "账户是否有效"
|
||||||
|
|
||||||
|
#: users/serializers/user.py:83
|
||||||
|
msgid "Is expired"
|
||||||
|
msgstr " 是否过期"
|
||||||
|
|
||||||
|
#: users/serializers/user.py:84
|
||||||
|
msgid "Avatar url"
|
||||||
|
msgstr "头像路径"
|
||||||
|
|
||||||
|
#: users/serializers/user.py:88
|
||||||
|
msgid "Groups name"
|
||||||
|
msgstr "用户组名"
|
||||||
|
|
||||||
|
#: users/serializers/user.py:89
|
||||||
|
msgid "Source name"
|
||||||
|
msgstr "用户来源名"
|
||||||
|
|
||||||
|
#: users/serializers/user.py:91
|
||||||
msgid "Super role name"
|
msgid "Super role name"
|
||||||
msgstr "超级角色名称"
|
msgstr "超级角色名称"
|
||||||
|
|
||||||
#: users/serializers/user.py:105
|
#: users/serializers/user.py:114
|
||||||
msgid "Role limit to {}"
|
msgid "Role limit to {}"
|
||||||
msgstr "角色只能为 {}"
|
msgstr "角色只能为 {}"
|
||||||
|
|
||||||
#: users/serializers/user.py:117 users/serializers/user.py:290
|
#: users/serializers/user.py:126 users/serializers/user.py:300
|
||||||
msgid "Password does not match security rules"
|
msgid "Password does not match security rules"
|
||||||
msgstr "密码不满足安全规则"
|
msgstr "密码不满足安全规则"
|
||||||
|
|
||||||
#: users/serializers/user.py:282
|
#: users/serializers/user.py:292
|
||||||
msgid "The old password is incorrect"
|
msgid "The old password is incorrect"
|
||||||
msgstr "旧密码错误"
|
msgstr "旧密码错误"
|
||||||
|
|
||||||
#: users/serializers/user.py:296
|
#: users/serializers/user.py:306
|
||||||
msgid "The newly set password is inconsistent"
|
msgid "The newly set password is inconsistent"
|
||||||
msgstr "两次密码不一致"
|
msgstr "两次密码不一致"
|
||||||
|
|
||||||
|
@ -3992,18 +4019,15 @@ msgstr "旗舰版"
|
||||||
#~ msgid "Role name"
|
#~ msgid "Role name"
|
||||||
#~ msgstr "角色名"
|
#~ msgstr "角色名"
|
||||||
|
|
||||||
#~ msgid "GUI copy"
|
|
||||||
#~ msgstr "GUI 复制"
|
|
||||||
|
|
||||||
#~ msgid "GUI paste"
|
|
||||||
#~ msgstr "GUI 粘贴"
|
|
||||||
|
|
||||||
#~ msgid "Covered always"
|
#~ msgid "Covered always"
|
||||||
#~ msgstr "总是被覆盖"
|
#~ msgstr "总是被覆盖"
|
||||||
|
|
||||||
#~ msgid "Account name"
|
#~ msgid "Account name"
|
||||||
#~ msgstr "账户名称"
|
#~ msgstr "账户名称"
|
||||||
|
|
||||||
|
#~ msgid "Superuser"
|
||||||
|
#~ msgstr "超级管理员"
|
||||||
|
|
||||||
#~ msgid "Auditors cannot be join in the user group"
|
#~ msgid "Auditors cannot be join in the user group"
|
||||||
#~ msgstr "审计员不能被加入到用户组"
|
#~ msgstr "审计员不能被加入到用户组"
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,6 @@ class OrgModelMixin(models.Model):
|
||||||
org = get_current_org()
|
org = get_current_org()
|
||||||
if org is None:
|
if org is None:
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
if org.is_real() or org.is_system():
|
if org.is_real() or org.is_system():
|
||||||
self.org_id = org.id
|
self.org_id = org.id
|
||||||
elif org.is_default():
|
elif org.is_default():
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.db.models import F
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.request import Request
|
||||||
|
|
||||||
|
from orgs.models import Organization, ROLE as ORG_ROLE
|
||||||
from users.models.user import User
|
from users.models.user import User
|
||||||
from common.const.http import POST, GET
|
from common.const.http import POST, GET
|
||||||
from common.drf.api import JMSModelViewSet
|
from common.drf.api import JMSModelViewSet
|
||||||
from common.permissions import IsValidUser
|
from common.permissions import IsValidUser
|
||||||
from common.utils.django import get_object_or_none
|
from common.utils.django import get_object_or_none
|
||||||
|
from common.utils.timezone import dt_parser
|
||||||
from common.drf.serializers import EmptySerializer
|
from common.drf.serializers import EmptySerializer
|
||||||
from perms.models.asset_permission import AssetPermission, Asset
|
from perms.models.asset_permission import AssetPermission, Asset
|
||||||
from assets.models.user import SystemUser
|
from assets.models.user import SystemUser
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
ConfirmedAssetsChanged, ConfirmedSystemUserChanged,
|
ConfirmedAssetsChanged, ConfirmedSystemUserChanged,
|
||||||
TicketClosed, TicketActionYet, NotHaveConfirmedAssets,
|
TicketClosed, TicketActionAlready, NotHaveConfirmedAssets,
|
||||||
NotHaveConfirmedSystemUser
|
NotHaveConfirmedSystemUser
|
||||||
)
|
)
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
@ -25,15 +26,15 @@ from ..permissions import IsAssignee
|
||||||
|
|
||||||
|
|
||||||
class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
||||||
queryset = Ticket.objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM)
|
queryset = Ticket.origin_objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM)
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.RequestAssetPermTicketSerializer,
|
'default': serializers.RequestAssetPermTicketSerializer,
|
||||||
'approve': EmptySerializer,
|
'approve': EmptySerializer,
|
||||||
'reject': EmptySerializer,
|
'reject': EmptySerializer,
|
||||||
'assignees': serializers.OrgAssigneeSerializer,
|
'assignees': serializers.AssigneeSerializer,
|
||||||
}
|
}
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsValidUser,)
|
||||||
filter_fields = ['status', 'title', 'action', 'user_display']
|
filter_fields = ['status', 'title', 'action', 'user_display', 'org_id']
|
||||||
search_fields = ['user_display', 'title']
|
search_fields = ['user_display', 'title']
|
||||||
|
|
||||||
def _check_can_set_action(self, instance, action):
|
def _check_can_set_action(self, instance, action):
|
||||||
|
@ -41,49 +42,39 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
||||||
raise TicketClosed(detail=_('Ticket closed'))
|
raise TicketClosed(detail=_('Ticket closed'))
|
||||||
if instance.action == action:
|
if instance.action == action:
|
||||||
action_display = dict(instance.ACTION_CHOICES).get(action)
|
action_display = dict(instance.ACTION_CHOICES).get(action)
|
||||||
raise TicketActionYet(detail=_('Ticket has %s') % action_display)
|
raise TicketActionAlready(detail=_('Ticket has %s') % action_display)
|
||||||
|
|
||||||
@action(detail=False, methods=[GET], permission_classes=[IsValidUser])
|
@action(detail=False, methods=[GET], permission_classes=[IsValidUser])
|
||||||
def assignees(self, request, *args, **kwargs):
|
def assignees(self, request: Request, *args, **kwargs):
|
||||||
org_mapper = {}
|
|
||||||
UserTuple = namedtuple('UserTuple', ('id', 'name', 'username'))
|
|
||||||
user = request.user
|
user = request.user
|
||||||
superusers = User.objects.filter(role=User.ROLE.ADMIN)
|
org_id = request.query_params.get('org_id', Organization.DEFAULT_ID)
|
||||||
|
|
||||||
admins_with_org = User.objects.filter(related_admin_orgs__users=user).annotate(
|
q = Q(role=User.ROLE.ADMIN)
|
||||||
org_id=F('related_admin_orgs__id'), org_name=F('related_admin_orgs__name')
|
if org_id != Organization.DEFAULT_ID:
|
||||||
)
|
q |= Q(m2m_org_members__role=ORG_ROLE.ADMIN, orgs__id=org_id, orgs__members=user)
|
||||||
|
org_admins = User.objects.filter(q).distinct()
|
||||||
|
|
||||||
for user in admins_with_org:
|
return self.get_paginated_response_with_query_set(org_admins)
|
||||||
org_id = user.org_id
|
|
||||||
|
|
||||||
if org_id not in org_mapper:
|
def _get_extra_comment(self, instance):
|
||||||
org_mapper[org_id] = {
|
meta = instance.meta
|
||||||
'org_name': user.org_name,
|
ips = ', '.join(meta.get('ips', []))
|
||||||
'org_admins': set() # 去重
|
confirmed_assets = ', '.join(meta.get('confirmed_assets', []))
|
||||||
}
|
|
||||||
org_mapper[org_id]['org_admins'].add(UserTuple(user.id, user.name, user.username))
|
|
||||||
|
|
||||||
result = [
|
return f'''
|
||||||
{
|
{_('IP group')}: {ips}
|
||||||
'org_name': _('Superuser'),
|
{_('Hostname')}: {meta.get('hostname', '')}
|
||||||
'org_admins': set(UserTuple(user.id, user.name, user.username)
|
{_('System user')}: {meta.get('system_user', '')}
|
||||||
for user in superusers)
|
{_('Confirmed assets')}: {confirmed_assets}
|
||||||
}
|
{_('Confirmed system user')}: {meta.get('confirmed_system_user', '')}
|
||||||
]
|
'''
|
||||||
|
|
||||||
for org in org_mapper.values():
|
|
||||||
result.append(org)
|
|
||||||
serializer_class = self.get_serializer_class()
|
|
||||||
serilizer = serializer_class(instance=result, many=True)
|
|
||||||
return Response(data=serilizer.data)
|
|
||||||
|
|
||||||
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
||||||
def reject(self, request, *args, **kwargs):
|
def reject(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
action = instance.ACTION_REJECT
|
action = instance.ACTION_REJECT
|
||||||
self._check_can_set_action(instance, action)
|
self._check_can_set_action(instance, action)
|
||||||
instance.perform_action(action, request.user)
|
instance.perform_action(action, request.user, self._get_extra_comment(instance))
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
||||||
|
@ -109,29 +100,33 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
||||||
if system_user is None:
|
if system_user is None:
|
||||||
raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed'))
|
raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed'))
|
||||||
|
|
||||||
self._create_asset_permission(instance, assets, system_user)
|
self._create_asset_permission(instance, assets, system_user, request.user)
|
||||||
return Response({'detail': _('Succeed')})
|
return Response({'detail': _('Succeed')})
|
||||||
|
|
||||||
def _create_asset_permission(self, instance: Ticket, assets, system_user):
|
def _create_asset_permission(self, instance: Ticket, assets, system_user, user):
|
||||||
meta = instance.meta
|
meta = instance.meta
|
||||||
request = self.request
|
request = self.request
|
||||||
|
|
||||||
ap_kwargs = {
|
ap_kwargs = {
|
||||||
'name': meta.get('name', ''),
|
'name': _('From request ticket: {} {}').format(instance.user_display, instance.id),
|
||||||
'created_by': self.request.user.username,
|
'created_by': self.request.user.username,
|
||||||
'comment': _('{} request assets, approved by {}').format(instance.user_display,
|
'comment': _('{} request assets, approved by {}').format(instance.user_display,
|
||||||
instance.assignee_display)
|
instance.assignees_display)
|
||||||
}
|
}
|
||||||
date_start = meta.get('date_start')
|
date_start = dt_parser(meta.get('date_start'))
|
||||||
date_expired = meta.get('date_expired')
|
date_expired = dt_parser(meta.get('date_expired'))
|
||||||
if date_start:
|
if date_start:
|
||||||
ap_kwargs['date_start'] = date_start
|
ap_kwargs['date_start'] = date_start
|
||||||
if date_expired:
|
if date_expired:
|
||||||
ap_kwargs['date_expired'] = date_expired
|
ap_kwargs['date_expired'] = date_expired
|
||||||
|
|
||||||
with atomic():
|
with atomic():
|
||||||
instance.perform_action(instance.ACTION_APPROVE, request.user)
|
instance.perform_action(instance.ACTION_APPROVE,
|
||||||
|
request.user,
|
||||||
|
self._get_extra_comment(instance))
|
||||||
ap = AssetPermission.objects.create(**ap_kwargs)
|
ap = AssetPermission.objects.create(**ap_kwargs)
|
||||||
ap.system_users.add(system_user)
|
ap.system_users.add(system_user)
|
||||||
ap.assets.add(*assets)
|
ap.assets.add(*assets)
|
||||||
|
ap.users.add(user)
|
||||||
|
|
||||||
return ap
|
return ap
|
||||||
|
|
|
@ -11,7 +11,7 @@ from .. import serializers, models, mixins
|
||||||
|
|
||||||
class TicketViewSet(mixins.TicketMixin, viewsets.ModelViewSet):
|
class TicketViewSet(mixins.TicketMixin, viewsets.ModelViewSet):
|
||||||
serializer_class = serializers.TicketSerializer
|
serializer_class = serializers.TicketSerializer
|
||||||
queryset = models.Ticket.objects.all()
|
queryset = models.Ticket.origin_objects.all()
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsValidUser,)
|
||||||
filter_fields = ['status', 'title', 'action', 'user_display']
|
filter_fields = ['status', 'title', 'action', 'user_display']
|
||||||
search_fields = ['user_display', 'title']
|
search_fields = ['user_display', 'title']
|
||||||
|
|
|
@ -21,5 +21,9 @@ class TicketClosed(JMSException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TicketActionYet(JMSException):
|
class TicketActionAlready(JMSException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OrgIdRequiredException(JMSException):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 2.2.10 on 2020-07-23 04:32
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('tickets', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='ticket',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('request_asset', 'Request asset permission')], default='general', max_length=16, verbose_name='Type'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 2.2.10 on 2020-07-28 03:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.manager
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tickets', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelManagers(
|
||||||
|
name='ticket',
|
||||||
|
managers=[
|
||||||
|
('origin_objects', django.db.models.manager.Manager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ticket',
|
||||||
|
name='org_id',
|
||||||
|
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ticket',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('request_asset', 'Request asset permission')], default='general', max_length=16, verbose_name='Type'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -6,11 +6,12 @@ from .models import Ticket
|
||||||
|
|
||||||
class TicketMixin:
|
class TicketMixin:
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
assign = self.request.GET.get('assign', None)
|
assign = self.request.GET.get('assign', None)
|
||||||
if assign is None:
|
if assign is None:
|
||||||
queryset = Ticket.get_related_tickets(self.request.user)
|
queryset = Ticket.get_related_tickets(self.request.user, queryset)
|
||||||
elif assign in ['1']:
|
elif assign in ['1']:
|
||||||
queryset = Ticket.get_assigned_tickets(self.request.user)
|
queryset = Ticket.get_assigned_tickets(self.request.user, queryset)
|
||||||
else:
|
else:
|
||||||
queryset = Ticket.get_my_tickets(self.request.user)
|
queryset = Ticket.get_my_tickets(self.request.user, queryset)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
|
@ -7,11 +7,12 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.mixins.models import CommonModelMixin
|
from common.mixins.models import CommonModelMixin
|
||||||
from common.fields.model import JsonDictTextField
|
from common.fields.model import JsonDictTextField
|
||||||
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
|
||||||
__all__ = ['Ticket', 'Comment']
|
__all__ = ['Ticket', 'Comment']
|
||||||
|
|
||||||
|
|
||||||
class Ticket(CommonModelMixin):
|
class Ticket(OrgModelMixin, CommonModelMixin):
|
||||||
STATUS_OPEN = 'open'
|
STATUS_OPEN = 'open'
|
||||||
STATUS_CLOSED = 'closed'
|
STATUS_CLOSED = 'closed'
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
|
@ -46,6 +47,8 @@ class Ticket(CommonModelMixin):
|
||||||
status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='open')
|
status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='open')
|
||||||
action = models.CharField(choices=ACTION_CHOICES, max_length=16, default='', blank=True)
|
action = models.CharField(choices=ACTION_CHOICES, max_length=16, default='', blank=True)
|
||||||
|
|
||||||
|
origin_objects = models.Manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}: {}'.format(self.user_display, self.title)
|
return '{}: {}'.format(self.user_display, self.title)
|
||||||
|
|
||||||
|
@ -79,13 +82,15 @@ class Ticket(CommonModelMixin):
|
||||||
self.status = status
|
self.status = status
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def create_action_comment(self, action, user):
|
def create_action_comment(self, action, user, extra_comment=None):
|
||||||
action_display = dict(self.ACTION_CHOICES).get(action)
|
action_display = dict(self.ACTION_CHOICES).get(action)
|
||||||
body = '{} {} {}'.format(user, action_display, _("this ticket"))
|
body = '{} {} {}'.format(user, action_display, _("this ticket"))
|
||||||
|
if extra_comment is not None:
|
||||||
|
body += extra_comment
|
||||||
self.comments.create(body=body, user=user, user_display=str(user))
|
self.comments.create(body=body, user=user, user_display=str(user))
|
||||||
|
|
||||||
def perform_action(self, action, user):
|
def perform_action(self, action, user, extra_comment=None):
|
||||||
self.create_action_comment(action, user)
|
self.create_action_comment(action, user, extra_comment)
|
||||||
self.action = action
|
self.action = action
|
||||||
self.status = self.STATUS_CLOSED
|
self.status = self.STATUS_CLOSED
|
||||||
self.assignee = user
|
self.assignee = user
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from common.utils.timezone import dt_parser, dt_formater
|
||||||
|
from orgs.utils import tmp_to_root_org
|
||||||
|
from orgs.models import Organization, ROLE as ORG_ROLE
|
||||||
|
from assets.models.asset import Asset
|
||||||
from users.models.user import User
|
from users.models.user import User
|
||||||
from ..models import Ticket
|
from ..models import Ticket
|
||||||
|
|
||||||
|
@ -22,9 +29,8 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
||||||
source='meta.confirmed_assets',
|
source='meta.confirmed_assets',
|
||||||
default=list, required=False,
|
default=list, required=False,
|
||||||
label=_('Confirmed assets'))
|
label=_('Confirmed assets'))
|
||||||
confirmed_system_user = serializers.ListField(child=serializers.UUIDField(),
|
confirmed_system_user = serializers.UUIDField(source='meta.confirmed_system_user',
|
||||||
source='meta.confirmed_system_user',
|
default='', required=False,
|
||||||
default=list, required=False,
|
|
||||||
label=_('Confirmed system user'))
|
label=_('Confirmed system user'))
|
||||||
assets_waitlist_url = serializers.SerializerMethodField()
|
assets_waitlist_url = serializers.SerializerMethodField()
|
||||||
system_user_waitlist_url = serializers.SerializerMethodField()
|
system_user_waitlist_url = serializers.SerializerMethodField()
|
||||||
|
@ -36,7 +42,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
||||||
'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
|
'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
|
||||||
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
|
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
|
||||||
'date_start', 'date_expired', 'confirmed_system_user', 'hostname',
|
'date_start', 'date_expired', 'confirmed_system_user', 'hostname',
|
||||||
'assets_waitlist_url', 'system_user'
|
'assets_waitlist_url', 'system_user', 'org_id'
|
||||||
]
|
]
|
||||||
m2m_fields = [
|
m2m_fields = [
|
||||||
'user', 'user_display', 'assignees', 'assignees_display',
|
'user', 'user_display', 'assignees', 'assignees_display',
|
||||||
|
@ -52,26 +58,44 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'status': {'label': _('Status')},
|
'status': {'label': _('Status')},
|
||||||
'action': {'label': _('Action')},
|
'action': {'label': _('Action')},
|
||||||
'user_display': {'label': _('User')}
|
'user_display': {'label': _('User')},
|
||||||
|
'org_id': {'required': True}
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_assignees(self, assignees):
|
def validate(self, attrs):
|
||||||
|
org_id = attrs.get('org_id')
|
||||||
|
assignees = attrs.get('assignees')
|
||||||
|
|
||||||
|
instance = self.instance
|
||||||
|
if instance is not None:
|
||||||
|
if org_id and not assignees:
|
||||||
|
assignees = list(instance.assignees.all())
|
||||||
|
elif assignees and not org_id:
|
||||||
|
org_id = instance.org_id
|
||||||
|
elif assignees and org_id:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return attrs
|
||||||
|
|
||||||
user = self.context['request'].user
|
user = self.context['request'].user
|
||||||
|
org = Organization.get_instance(org_id)
|
||||||
|
if org is None:
|
||||||
|
raise serializers.ValidationError(_('Invalid `org_id`'))
|
||||||
|
|
||||||
count = User.objects.filter(Q(related_admin_orgs__users=user) | Q(role=User.ROLE.ADMIN)).filter(
|
q = Q(role=User.ROLE.ADMIN)
|
||||||
id__in=[assignee.id for assignee in assignees]).distinct().count()
|
if not org.is_default():
|
||||||
|
q |= Q(m2m_org_members__role=ORG_ROLE.ADMIN, orgs__id=org_id, orgs__members=user)
|
||||||
|
|
||||||
|
q &= Q(id__in=[assignee.id for assignee in assignees])
|
||||||
|
count = User.objects.filter(q).distinct().count()
|
||||||
if count != len(assignees):
|
if count != len(assignees):
|
||||||
raise serializers.ValidationError(_('Must be organization admin or superuser'))
|
raise serializers.ValidationError(_('Field `assignees` must be organization admin or superuser'))
|
||||||
return assignees
|
return attrs
|
||||||
|
|
||||||
def get_system_user_waitlist_url(self, instance: Ticket):
|
def get_system_user_waitlist_url(self, instance: Ticket):
|
||||||
if not self._is_assignee(instance):
|
if not self._is_assignee(instance):
|
||||||
return None
|
return None
|
||||||
meta = instance.meta
|
return reverse('api-assets:system-user-list')
|
||||||
url = reverse('api-assets:system-user-list')
|
|
||||||
query = meta.get('system_user', '')
|
|
||||||
return '{}?search={}'.format(url, query)
|
|
||||||
|
|
||||||
def get_assets_waitlist_url(self, instance: Ticket):
|
def get_assets_waitlist_url(self, instance: Ticket):
|
||||||
if not self._is_assignee(instance):
|
if not self._is_assignee(instance):
|
||||||
|
@ -81,37 +105,106 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
||||||
query = ''
|
query = ''
|
||||||
|
|
||||||
meta = instance.meta
|
meta = instance.meta
|
||||||
ips = meta.get('ips', [])
|
|
||||||
hostname = meta.get('hostname')
|
hostname = meta.get('hostname')
|
||||||
|
if hostname:
|
||||||
if ips:
|
|
||||||
query = '?ips=%s' % ','.join(ips)
|
|
||||||
elif hostname:
|
|
||||||
query = '?search=%s' % hostname
|
query = '?search=%s' % hostname
|
||||||
|
|
||||||
return asset_api + query
|
return asset_api + query
|
||||||
|
|
||||||
|
def _recommend_assets(self, data, instance):
|
||||||
|
confirmed_assets = data.get('confirmed_assets')
|
||||||
|
if not confirmed_assets and self._is_assignee(instance):
|
||||||
|
ips = data.get('ips')
|
||||||
|
hostname = data.get('hostname')
|
||||||
|
limit = 5
|
||||||
|
|
||||||
|
q = Q(id=None)
|
||||||
|
if ips:
|
||||||
|
limit = len(ips) + 2
|
||||||
|
q |= Q(ip__in=ips)
|
||||||
|
if hostname:
|
||||||
|
q |= Q(hostname__icontains=hostname)
|
||||||
|
|
||||||
|
data['confirmed_assets'] = list(
|
||||||
|
map(lambda x: str(x), chain(*Asset.objects.filter(q)[0: limit].values_list('id'))))
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
data = super().to_representation(instance)
|
||||||
|
self._recommend_assets(data, instance)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _create_body(self, validated_data):
|
||||||
|
meta = validated_data['meta']
|
||||||
|
type = dict(Ticket.TYPE_CHOICES).get(validated_data.get('type', ''))
|
||||||
|
date_start = dt_parser(meta.get('date_start')).strftime(settings.DATETIME_DISPLAY_FORMAT)
|
||||||
|
date_expired = dt_parser(meta.get('date_expired')).strftime(settings.DATETIME_DISPLAY_FORMAT)
|
||||||
|
|
||||||
|
validated_data['body'] = _('''
|
||||||
|
Type: {type}<br>
|
||||||
|
User: {username}<br>
|
||||||
|
Ip group: {ips}<br>
|
||||||
|
Hostname: {hostname}<br>
|
||||||
|
System user: {system_user}<br>
|
||||||
|
Date start: {date_start}<br>
|
||||||
|
Date expired: {date_expired}<br>
|
||||||
|
''').format(
|
||||||
|
type=type,
|
||||||
|
username=validated_data.get('user', ''),
|
||||||
|
ips=', '.join(meta.get('ips', [])),
|
||||||
|
hostname=meta.get('hostname', ''),
|
||||||
|
system_user=meta.get('system_user', ''),
|
||||||
|
date_start=date_start,
|
||||||
|
date_expired=date_expired
|
||||||
|
)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
# `type` 与 `user` 用户不可提交,
|
||||||
validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM
|
validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM
|
||||||
validated_data['user'] = self.context['request'].user
|
validated_data['user'] = self.context['request'].user
|
||||||
|
# `confirmed` 相关字段只能审批人修改,所以创建时直接清理掉
|
||||||
self._pop_confirmed_fields()
|
self._pop_confirmed_fields()
|
||||||
|
self._create_body(validated_data)
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
|
"""
|
||||||
|
做了一些数据转换
|
||||||
|
"""
|
||||||
meta = self.validated_data.get('meta', {})
|
meta = self.validated_data.get('meta', {})
|
||||||
|
|
||||||
|
org_id = self.validated_data.get('org_id')
|
||||||
|
if org_id is not None and org_id == Organization.DEFAULT_ID:
|
||||||
|
self.validated_data['org_id'] = ''
|
||||||
|
|
||||||
|
# 时间的转换,好烦😭,可能有更好的办法吧
|
||||||
date_start = meta.get('date_start')
|
date_start = meta.get('date_start')
|
||||||
if date_start:
|
if date_start:
|
||||||
meta['date_start'] = date_start.strftime('%Y-%m-%d %H:%M:%S%z')
|
meta['date_start'] = dt_formater(date_start)
|
||||||
|
|
||||||
date_expired = meta.get('date_expired')
|
date_expired = meta.get('date_expired')
|
||||||
if date_expired:
|
if date_expired:
|
||||||
meta['date_expired'] = date_expired.strftime('%Y-%m-%d %H:%M:%S%z')
|
meta['date_expired'] = dt_formater(date_expired)
|
||||||
return super().save(**kwargs)
|
|
||||||
|
# UUID 的转换
|
||||||
|
confirmed_system_user = meta.get('confirmed_system_user')
|
||||||
|
if confirmed_system_user:
|
||||||
|
meta['confirmed_system_user'] = str(confirmed_system_user)
|
||||||
|
|
||||||
|
confirmed_assets = meta.get('confirmed_assets')
|
||||||
|
if confirmed_assets:
|
||||||
|
new_confirmed_assets = []
|
||||||
|
for asset in confirmed_assets:
|
||||||
|
new_confirmed_assets.append(str(asset))
|
||||||
|
meta['confirmed_assets'] = new_confirmed_assets
|
||||||
|
with tmp_to_root_org():
|
||||||
|
return super().save(**kwargs)
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
new_meta = validated_data['meta']
|
new_meta = validated_data['meta']
|
||||||
if not self._is_assignee(instance):
|
if not self._is_assignee(instance):
|
||||||
self._pop_confirmed_fields()
|
self._pop_confirmed_fields()
|
||||||
|
|
||||||
|
# Json 字段保存的坑😭
|
||||||
old_meta = instance.meta
|
old_meta = instance.meta
|
||||||
meta = {}
|
meta = {}
|
||||||
meta.update(old_meta)
|
meta.update(old_meta)
|
||||||
|
@ -134,8 +227,3 @@ class AssigneeSerializer(serializers.Serializer):
|
||||||
id = serializers.UUIDField()
|
id = serializers.UUIDField()
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
username = serializers.CharField()
|
username = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class OrgAssigneeSerializer(serializers.Serializer):
|
|
||||||
org_name = serializers.CharField()
|
|
||||||
org_admins = AssigneeSerializer(many=True)
|
|
||||||
|
|
|
@ -1,3 +1,89 @@
|
||||||
from django.test import TestCase
|
import datetime
|
||||||
|
|
||||||
# Create your tests here.
|
from common.utils.timezone import now
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from orgs.models import Organization, OrganizationMember, ROLE as ORG_ROLE
|
||||||
|
from orgs.utils import set_current_org
|
||||||
|
from users.models.user import User
|
||||||
|
from assets.models import Asset, AdminUser, SystemUser
|
||||||
|
|
||||||
|
|
||||||
|
class TicketTest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
Organization.objects.bulk_create([
|
||||||
|
Organization(name='org-01'),
|
||||||
|
Organization(name='org-02'),
|
||||||
|
Organization(name='org-03'),
|
||||||
|
])
|
||||||
|
org_01, org_02, org_03 = Organization.objects.all()
|
||||||
|
self.org_01, self.org_02, self.org_03 = org_01, org_02, org_03
|
||||||
|
|
||||||
|
set_current_org(org_01)
|
||||||
|
|
||||||
|
AdminUser.objects.bulk_create([
|
||||||
|
AdminUser(name='au-01', username='au-01'),
|
||||||
|
AdminUser(name='au-02', username='au-02'),
|
||||||
|
AdminUser(name='au-03', username='au-03'),
|
||||||
|
])
|
||||||
|
|
||||||
|
SystemUser.objects.bulk_create([
|
||||||
|
SystemUser(name='su-01', username='su-01'),
|
||||||
|
SystemUser(name='su-02', username='su-02'),
|
||||||
|
SystemUser(name='su-03', username='su-03'),
|
||||||
|
])
|
||||||
|
|
||||||
|
admin_users = AdminUser.objects.all()
|
||||||
|
Asset.objects.bulk_create([
|
||||||
|
Asset(hostname='asset-01', ip='192.168.1.1', public_ip='192.168.1.1', admin_user=admin_users[0]),
|
||||||
|
Asset(hostname='asset-02', ip='192.168.1.2', public_ip='192.168.1.2', admin_user=admin_users[0]),
|
||||||
|
Asset(hostname='asset-03', ip='192.168.1.3', public_ip='192.168.1.3', admin_user=admin_users[0]),
|
||||||
|
])
|
||||||
|
|
||||||
|
new_user = User.objects.create
|
||||||
|
new_org_memeber = OrganizationMember.objects.create
|
||||||
|
|
||||||
|
u = new_user(name='user-01', username='user-01', email='user-01@jms.com')
|
||||||
|
new_org_memeber(org=org_01, user=u, role=ORG_ROLE.USER)
|
||||||
|
new_org_memeber(org=org_02, user=u, role=ORG_ROLE.USER)
|
||||||
|
self.user_01 = u
|
||||||
|
|
||||||
|
u = new_user(name='org-admin-01', username='org-admin-01', email='org-admin-01@jms.com')
|
||||||
|
new_org_memeber(org=org_01, user=u, role=ORG_ROLE.ADMIN)
|
||||||
|
self.org_admin_01 = u
|
||||||
|
|
||||||
|
u = new_user(name='org-admin-02', username='org-admin-02', email='org-admin-02@jms.com')
|
||||||
|
new_org_memeber(org=org_02, user=u, role=ORG_ROLE.ADMIN)
|
||||||
|
self.org_admin_02 = u
|
||||||
|
|
||||||
|
def test_create_request_asset_perm(self):
|
||||||
|
url = reverse('api-tickets:ticket-request-asset-perm')
|
||||||
|
ticket_url = reverse('api-tickets:ticket')
|
||||||
|
|
||||||
|
self.client.force_login(self.user_01)
|
||||||
|
|
||||||
|
date_start = now()
|
||||||
|
date_expired = date_start + datetime.timedelta(days=7)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"title": "request-01",
|
||||||
|
"ips": [
|
||||||
|
"192.168.1.1"
|
||||||
|
],
|
||||||
|
"date_start": date_start,
|
||||||
|
"date_expired": date_expired,
|
||||||
|
"hostname": "",
|
||||||
|
"system_user": "",
|
||||||
|
"org_id": self.org_01.id,
|
||||||
|
"assignees": [
|
||||||
|
str(self.org_admin_01.id),
|
||||||
|
str(self.org_admin_02.id),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.post(data)
|
||||||
|
|
||||||
|
self.client.force_login(self.org_admin_01)
|
||||||
|
res = self.client.get(ticket_url, params={'assgin': 1})
|
||||||
|
|
|
@ -7,7 +7,7 @@ from .. import api
|
||||||
app_name = 'tickets'
|
app_name = 'tickets'
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
|
|
||||||
# router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
|
router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
|
||||||
router.register('tickets', api.TicketViewSet, 'ticket')
|
router.register('tickets', api.TicketViewSet, 'ticket')
|
||||||
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from urllib.parse import urljoin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from common.utils import get_logger, reverse
|
from common.utils import get_logger
|
||||||
from common.tasks import send_mail_async
|
from common.tasks import send_mail_async
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
from tickets.models import Ticket
|
||||||
|
|
||||||
|
|
||||||
def send_new_ticket_mail_to_assignees(ticket, assignees):
|
def send_new_ticket_mail_to_assignees(ticket: Ticket, assignees):
|
||||||
recipient_list = [user.email for user in assignees]
|
recipient_list = [user.email for user in assignees]
|
||||||
user = ticket.user
|
user = ticket.user
|
||||||
if not recipient_list:
|
if not recipient_list:
|
||||||
logger.error("Ticket not has assignees: {}".format(ticket.id))
|
logger.error("Ticket not has assignees: {}".format(ticket.id))
|
||||||
return
|
return
|
||||||
subject = '{}: {}'.format(_("New ticket"), ticket.title)
|
subject = '{}: {}'.format(_("New ticket"), ticket.title)
|
||||||
detail_url = reverse('tickets:ticket-detail',
|
|
||||||
kwargs={'pk': ticket.id}, external=True)
|
# 这里要设置前端地址,因为要直接跳转到页面
|
||||||
|
if ticket.type == ticket.TYPE_REQUEST_ASSET_PERM:
|
||||||
|
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/request-asset-perm/{ticket.id}')
|
||||||
|
else:
|
||||||
|
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/{ticket.id}')
|
||||||
|
|
||||||
message = _("""
|
message = _("""
|
||||||
<div>
|
<div>
|
||||||
<p>Your has a new ticket</p>
|
<p>Your has a new ticket</p>
|
||||||
|
|
|
@ -233,6 +233,11 @@ class RoleMixin:
|
||||||
def is_app(self):
|
def is_app(self):
|
||||||
return self.role == self.ROLE.APP
|
return self.role == self.ROLE.APP
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def user_all_orgs(self):
|
||||||
|
from orgs.models import Organization
|
||||||
|
return Organization.get_user_all_orgs(self)
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def user_orgs(self):
|
def user_orgs(self):
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
|
|
|
@ -27,6 +27,11 @@ class UserOrgSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class UserOrgLabelSerializer(serializers.Serializer):
|
||||||
|
value = serializers.CharField(source='id')
|
||||||
|
label = serializers.CharField(source='name')
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
||||||
EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user')
|
EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user')
|
||||||
CUSTOM_PASSWORD = _('Set password')
|
CUSTOM_PASSWORD = _('Set password')
|
||||||
|
@ -214,6 +219,7 @@ class UserRoleSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class UserProfileSerializer(UserSerializer):
|
class UserProfileSerializer(UserSerializer):
|
||||||
admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True)
|
admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True)
|
||||||
|
user_all_orgs = UserOrgLabelSerializer(many=True, read_only=True)
|
||||||
current_org_roles = serializers.ListField(read_only=True)
|
current_org_roles = serializers.ListField(read_only=True)
|
||||||
public_key_comment = serializers.CharField(
|
public_key_comment = serializers.CharField(
|
||||||
source='get_public_key_comment', required=False, read_only=True, max_length=128
|
source='get_public_key_comment', required=False, read_only=True, max_length=128
|
||||||
|
@ -231,7 +237,7 @@ class UserProfileSerializer(UserSerializer):
|
||||||
class Meta(UserSerializer.Meta):
|
class Meta(UserSerializer.Meta):
|
||||||
fields = UserSerializer.Meta.fields + [
|
fields = UserSerializer.Meta.fields + [
|
||||||
'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles',
|
'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles',
|
||||||
'guide_url'
|
'guide_url', 'user_all_orgs'
|
||||||
]
|
]
|
||||||
extra_kwargs = dict(UserSerializer.Meta.extra_kwargs)
|
extra_kwargs = dict(UserSerializer.Meta.extra_kwargs)
|
||||||
extra_kwargs.update({
|
extra_kwargs.update({
|
||||||
|
|
Loading…
Reference in New Issue