\n"
@@ -97,6 +97,8 @@ msgstr "动作"
#: perms/models/base.py:45 templates/index.html:78
#: terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38
+#: terminal/templates/terminal/_msg_command_alert.html:10
+#: terminal/templates/terminal/_msg_command_execue_alert.html:4
#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:180
#: users/models/user.py:813 users/models/user.py:839
#: users/serializers/group.py:19
@@ -119,10 +121,11 @@ msgstr "系统用户"
#: acls/models/login_asset_acl.py:22
#: applications/serializers/attrs/application_category/remote_app.py:37
#: assets/models/asset.py:357 assets/models/authbook.py:18
-#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:207
+#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:215
#: audits/models.py:38 perms/models/asset_permission.py:99
#: templates/index.html:82 terminal/backends/command/models.py:19
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:40
+#: terminal/templates/terminal/_msg_command_alert.html:7
#: users/templates/users/user_asset_permission.html:40
#: users/templates/users/user_asset_permission.html:70
#: xpack/plugins/change_auth_plan/models/asset.py:195
@@ -155,7 +158,9 @@ msgstr ""
#: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31
#: applications/serializers/attrs/application_type/mysql_workbench.py:18
#: assets/models/asset.py:180 assets/models/domain.py:61
-#: assets/serializers/account.py:12 settings/serializers/terminal.py:8
+#: assets/serializers/account.py:12
+#: authentication/templates/authentication/_msg_rest_password_success.html:9
+#: settings/serializers/terminal.py:8
#: users/templates/users/_granted_assets.html:26
#: users/templates/users/user_asset_permission.html:156
msgid "IP"
@@ -177,6 +182,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: applications/serializers/attrs/application_type/vmware_client.py:26
#: assets/models/base.py:176 assets/models/gathered_user.py:15
#: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17
+#: authentication/templates/authentication/_msg_different_city.html:9
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:602
#: users/templates/users/_select_user_modal.html:14
#: xpack/plugins/change_auth_plan/models/asset.py:35
@@ -316,7 +322,7 @@ msgstr "类别名称"
#: applications/serializers/application.py:60
#: applications/serializers/application.py:91
-#: assets/serializers/system_user.py:26 audits/serializers.py:29
+#: assets/serializers/system_user.py:27 audits/serializers.py:29
#: perms/serializers/application/permission.py:17
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:35
#: tickets/serializers/ticket/ticket.py:22
@@ -421,7 +427,7 @@ msgstr "基础"
msgid "Charset"
msgstr "编码"
-#: assets/models/asset.py:142 assets/serializers/asset.py:172
+#: assets/models/asset.py:142 assets/serializers/asset.py:174
#: tickets/models/ticket.py:50
msgid "Meta"
msgstr "元数据"
@@ -431,11 +437,11 @@ msgid "Internal"
msgstr "内部的"
#: assets/models/asset.py:163 assets/models/asset.py:187
-#: assets/serializers/asset.py:63 perms/serializers/asset/user_permission.py:43
+#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:43
msgid "Platform"
msgstr "系统平台"
-#: assets/models/asset.py:186 assets/serializers/asset.py:65
+#: assets/models/asset.py:186 assets/serializers/asset.py:67
#: perms/serializers/asset/user_permission.py:41
#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:42
msgid "Protocols"
@@ -643,6 +649,8 @@ msgstr "正则表达式"
#: assets/models/cmd_filter.py:41 ops/models/command.py:25
#: terminal/backends/command/serializers.py:15 terminal/models/session.py:49
+#: terminal/templates/terminal/_msg_command_alert.html:4
+#: terminal/templates/terminal/_msg_command_execue_alert.html:15
msgid "Command"
msgstr "命令"
@@ -740,7 +748,7 @@ msgstr "全称"
msgid "Parent key"
msgstr "ssh私钥"
-#: assets/models/node.py:559 assets/serializers/system_user.py:206
+#: assets/models/node.py:559 assets/serializers/system_user.py:214
#: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158
@@ -764,8 +772,10 @@ msgstr "普通用户"
msgid "Username same with user"
msgstr "用户名与用户相同"
-#: assets/models/user.py:200 assets/serializers/domain.py:28
-#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models/asset.py:40
+#: assets/models/user.py:200 assets/serializers/domain.py:29
+#: templates/_nav.html:39
+#: terminal/templates/terminal/_msg_command_execue_alert.html:10
+#: xpack/plugins/change_auth_plan/models/asset.py:40
msgid "Assets"
msgstr "资产"
@@ -819,32 +829,32 @@ msgstr "%(value)s is not an even number"
msgid "System user display"
msgstr "系统用户名称"
-#: assets/serializers/asset.py:20
+#: assets/serializers/asset.py:22
msgid "Protocol format should {}/{}"
msgstr "协议格式 {}/{}"
-#: assets/serializers/asset.py:37
+#: assets/serializers/asset.py:39
msgid "Protocol duplicate: {}"
msgstr "协议重复: {}"
-#: assets/serializers/asset.py:66
+#: assets/serializers/asset.py:68
msgid "Domain name"
msgstr "网域名称"
-#: assets/serializers/asset.py:67
+#: assets/serializers/asset.py:69
msgid "Nodes name"
msgstr "节点名称"
-#: assets/serializers/asset.py:101
+#: assets/serializers/asset.py:103
msgid "Hardware info"
msgstr "硬件信息"
-#: assets/serializers/asset.py:102 assets/serializers/system_user.py:225
+#: assets/serializers/asset.py:104 assets/serializers/system_user.py:233
#: orgs/mixins/serializers.py:26
msgid "Org name"
msgstr "组织名称"
-#: assets/serializers/asset.py:103
+#: assets/serializers/asset.py:105
msgid "Admin user display"
msgstr "特权用户名称"
@@ -852,18 +862,18 @@ msgstr "特权用户名称"
msgid "private key invalid"
msgstr "密钥不合法"
-#: assets/serializers/domain.py:12 assets/serializers/label.py:12
-#: assets/serializers/system_user.py:52
+#: assets/serializers/domain.py:13 assets/serializers/label.py:12
+#: assets/serializers/system_user.py:53
#: perms/serializers/asset/permission.py:72
msgid "Assets amount"
msgstr "资产数量"
-#: assets/serializers/domain.py:13
+#: assets/serializers/domain.py:14
#: perms/serializers/application/permission.py:43
msgid "Applications amount"
msgstr "应用数量"
-#: assets/serializers/domain.py:14
+#: assets/serializers/domain.py:15
msgid "Gateways count"
msgstr "网关数量"
@@ -879,48 +889,52 @@ msgstr "不能包含: /"
msgid "The same level node name cannot be the same"
msgstr "同级别节点名字不能重复"
-#: assets/serializers/system_user.py:27
+#: assets/serializers/system_user.py:28
msgid "SSH key fingerprint"
msgstr "密钥指纹"
-#: assets/serializers/system_user.py:51
+#: assets/serializers/system_user.py:52
#: perms/serializers/asset/permission.py:73
msgid "Nodes amount"
msgstr "节点数量"
-#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:208
+#: assets/serializers/system_user.py:54 assets/serializers/system_user.py:216
msgid "Login mode display"
msgstr "认证方式名称"
-#: assets/serializers/system_user.py:55
+#: assets/serializers/system_user.py:56
msgid "Ad domain"
msgstr "Ad 网域"
-#: assets/serializers/system_user.py:56
+#: assets/serializers/system_user.py:57
msgid "Is asset protocol"
msgstr ""
-#: assets/serializers/system_user.py:96
+#: assets/serializers/system_user.py:97
msgid "Username same with user with protocol {} only allow 1"
msgstr "用户名和用户相同的一种协议只允许存在一个"
-#: assets/serializers/system_user.py:110
+#: assets/serializers/system_user.py:107 common/validators.py:14
+msgid "Special char not allowed"
+msgstr "不能包含特殊字符"
+
+#: assets/serializers/system_user.py:116
msgid "* Automatic login mode must fill in the username."
msgstr "自动登录模式,必须填写用户名"
-#: assets/serializers/system_user.py:124
+#: assets/serializers/system_user.py:130
msgid "Path should starts with /"
msgstr "路径应该以 / 开头"
-#: assets/serializers/system_user.py:149
+#: assets/serializers/system_user.py:155
msgid "Password or private key required"
msgstr "密码或密钥密码需要一个"
-#: assets/serializers/system_user.py:224
+#: assets/serializers/system_user.py:232
msgid "System user name"
msgstr "系统用户名称"
-#: assets/serializers/system_user.py:234
+#: assets/serializers/system_user.py:242
msgid "Asset hostname"
msgstr "资产主机名"
@@ -1150,6 +1164,7 @@ msgid "Login ip"
msgstr "登录IP"
#: audits/models.py:108
+#: authentication/templates/authentication/_msg_different_city.html:11
#: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:17
msgid "Login city"
msgstr "登录城市"
@@ -1639,10 +1654,8 @@ msgid "Your password is invalid"
msgstr "您的密码无效"
#: authentication/errors.py:357
-#, fuzzy
-#| msgid "Not has host {} permission"
msgid "No upload or download permission"
-msgstr "没有该主机 {} 权限"
+msgstr "没有上传下载权限"
#: authentication/forms.py:35
msgid "{} days auto login"
@@ -1668,6 +1681,10 @@ msgstr "动态码"
msgid "Please change your password"
msgstr "请修改密码"
+#: authentication/mixins.py:514
+msgid "SMS"
+msgstr "短信"
+
#: authentication/models.py:40
msgid "Private Token"
msgstr "SSH密钥"
@@ -1684,47 +1701,10 @@ msgstr "{} 需要 {} 复核"
msgid "Expired"
msgstr "过期时间"
-#: authentication/notifications.py:11
-#, python-brace-format
-msgid ""
-"{subject}
Dear {server_name} user, Hello!
Your account has "
-"remote login behavior, please pay attention.
User: {username}"
-"p>
Login time: {time}
Login location: {city} ({ip})
If you "
-"suspect that the login behavior is abnormal, please modify
the account "
-"password in time.
Thank you for your attention to {server_name}!"
-"p>"
-msgstr ""
-"
{subject}
尊敬的{server_name}用户, 您好!
您的账"
-"号存在异地登录行为,请关注。
用户: {username}
登录时间: {time}"
-"p>
登录地点: {city} ({ip})
若怀疑此次登录行为异常,请及时修改账号密"
-"码。
感谢您对{server_name}的关注!
"
-
-#: authentication/notifications.py:36
+#: authentication/notifications.py:19
msgid "Different city login reminder"
msgstr "异地登录提醒"
-#: authentication/notifications.py:40
-#, python-brace-format
-msgid ""
-"{subject}\n"
-"Dear {server_name} user, Hello!\n"
-"Your account has remote login behavior, please pay attention.\n"
-"User: {username}\n"
-"Login time: {time}\n"
-"Login location: {city} ({ip})\n"
-"If you suspect that the login behavior is abnormal, please modify the "
-"account password in time.\n"
-"Thank you for your attention to {server_name}!\n"
-msgstr ""
-"{subject}\n"
-"尊敬的{server_name}用户, 您好!\n"
-"您的账号存在异地登录行为,请关注。\n"
-"用户: {username}\n"
-"登录时间: {time}\n"
-"登录地点: {city} ({ip})\n"
-"若怀疑此次登录行为异常,请及时修改账号密码。\n"
-"感谢您对{server_name}的关注!\n"
-
#: authentication/sms_verify_code.py:15
msgid "The verification code has expired. Please resend it"
msgstr "验证码已过期,请重新发送"
@@ -1816,6 +1796,75 @@ msgstr "确认"
msgid "Code error"
msgstr "代码错误"
+#: authentication/templates/authentication/_msg_different_city.html:3
+#: authentication/templates/authentication/_msg_reset_password.html:2
+#: authentication/templates/authentication/_msg_rest_password_success.html:3
+#: perms/templates/perms/_msg_item_permissions_expire.html:3
+#: perms/templates/perms/_msg_permed_items_expire.html:3
+#: users/templates/users/_msg_account_expire_reminder.html:4
+#: users/templates/users/_msg_password_expire_reminder.html:4
+#: users/templates/users/_msg_reset_mfa.html:4
+#: users/templates/users/_msg_reset_ssh_key.html:4
+msgid "Hello"
+msgstr "你好"
+
+#: authentication/templates/authentication/_msg_different_city.html:6
+msgid "Your account has remote login behavior, please pay attention"
+msgstr "你的账号存在异地登录行为,请关注。"
+
+#: authentication/templates/authentication/_msg_different_city.html:10
+msgid "Login time"
+msgstr "登录日期"
+
+#: authentication/templates/authentication/_msg_different_city.html:16
+msgid ""
+"If you suspect that the login behavior is abnormal, please modify the "
+"account password in time."
+msgstr "若怀疑此次登录行为异常,请及时修改账号密码"
+
+#: authentication/templates/authentication/_msg_reset_password.html:4
+msgid ""
+"Please click the link below to reset your password, if not your request, "
+"concern your account security"
+msgstr "请点击下面链接重置密码, 如果不是您申请的,请关注账号安全"
+
+#: authentication/templates/authentication/_msg_reset_password.html:7
+msgid "Click here reset password"
+msgstr "点击这里重置密码"
+
+#: authentication/templates/authentication/_msg_reset_password.html:10
+msgid "This link is valid for 1 hour. After it expires,"
+msgstr "这个链接有效期1小时, 超过时间您可以"
+
+#: authentication/templates/authentication/_msg_reset_password.html:10
+msgid "request new one"
+msgstr "重新申请"
+
+#: authentication/templates/authentication/_msg_reset_password.html:14
+#: users/templates/users/_msg_password_expire_reminder.html:22
+#: users/templates/users/_msg_reset_mfa.html:11
+#: users/templates/users/_msg_reset_ssh_key.html:11
+msgid "Login direct"
+msgstr "直接登录"
+
+#: authentication/templates/authentication/_msg_rest_password_success.html:6
+msgid "Your password has just been successfully updated."
+msgstr "你的密码刚刚已经成功更新。"
+
+#: authentication/templates/authentication/_msg_rest_password_success.html:10
+msgid "Browser"
+msgstr "浏览器"
+
+#: authentication/templates/authentication/_msg_rest_password_success.html:15
+msgid ""
+"If the password update was not initiated by you, your account may have "
+"security issues."
+msgstr "如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题。"
+
+#: authentication/templates/authentication/_msg_rest_password_success.html:16
+msgid "If you have any questions, you can contact the administrator."
+msgstr "如果有疑问或需求,请联系系统管理员"
+
#: authentication/templates/authentication/login.html:143
msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录"
@@ -1863,98 +1912,89 @@ msgstr "返回"
msgid "Copy success"
msgstr "复制成功"
-#: authentication/views/dingtalk.py:41
+#: authentication/views/dingtalk.py:37
msgid "DingTalk Error, Please contact your system administrator"
msgstr "钉钉错误,请联系系统管理员"
-#: authentication/views/dingtalk.py:44
+#: authentication/views/dingtalk.py:40
msgid "DingTalk Error"
msgstr "钉钉错误"
-#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:52
-#: authentication/views/wecom.py:56
-msgid "You've been hacked"
-msgstr "你被攻击了"
+#: authentication/views/dingtalk.py:52 authentication/views/feishu.py:48
+#: authentication/views/wecom.py:52
+msgid ""
+"The system configuration is incorrect. Please contact your administrator"
+msgstr "企业配置错误,请联系系统管理员"
-#: authentication/views/dingtalk.py:92
+#: authentication/views/dingtalk.py:90
msgid "DingTalk is already bound"
msgstr "钉钉已经绑定"
-#: authentication/views/dingtalk.py:105 authentication/views/feishu.py:99
-#: authentication/views/wecom.py:104
+#: authentication/views/dingtalk.py:103 authentication/views/feishu.py:97
+#: authentication/views/wecom.py:102
msgid "Please verify your password first"
msgstr "请检查密码"
-#: authentication/views/dingtalk.py:129 authentication/views/wecom.py:128
+#: authentication/views/dingtalk.py:127 authentication/views/wecom.py:126
msgid "Invalid user_id"
msgstr "无效的 user_id"
-#: authentication/views/dingtalk.py:145
+#: authentication/views/dingtalk.py:143
msgid "DingTalk query user failed"
msgstr "钉钉查询用户失败"
-#: authentication/views/dingtalk.py:154
+#: authentication/views/dingtalk.py:152
msgid "The DingTalk is already bound to another user"
msgstr "该钉钉已经绑定其他用户"
-#: authentication/views/dingtalk.py:159 authentication/views/dingtalk.py:242
-#: authentication/views/dingtalk.py:243
+#: authentication/views/dingtalk.py:157
msgid "Binding DingTalk successfully"
msgstr "绑定 钉钉 成功"
-#: authentication/views/dingtalk.py:211
+#: authentication/views/dingtalk.py:209
msgid "Failed to get user from DingTalk"
msgstr "从钉钉获取用户失败"
-#: authentication/views/dingtalk.py:217
+#: authentication/views/dingtalk.py:215
msgid "DingTalk is not bound"
msgstr "钉钉没有绑定"
-#: authentication/views/dingtalk.py:218
+#: authentication/views/dingtalk.py:216
msgid "Please login with a password and then bind the DingTalk"
msgstr "请使用密码登录,然后绑定钉钉"
-#: authentication/views/dingtalk.py:260 authentication/views/dingtalk.py:261
-msgid "Binding DingTalk failed"
-msgstr "绑定钉钉失败"
-
-#: authentication/views/feishu.py:40
+#: authentication/views/feishu.py:36
msgid "FeiShu Error"
msgstr "飞书错误"
-#: authentication/views/feishu.py:86
+#: authentication/views/feishu.py:84
msgid "FeiShu is already bound"
msgstr "飞书已经绑定"
-#: authentication/views/feishu.py:133
+#: authentication/views/feishu.py:131
msgid "FeiShu query user failed"
msgstr "飞书查询用户失败"
-#: authentication/views/feishu.py:142
+#: authentication/views/feishu.py:140
msgid "The FeiShu is already bound to another user"
msgstr "该飞书已经绑定其他用户"
-#: authentication/views/feishu.py:147 authentication/views/feishu.py:229
-#: authentication/views/feishu.py:230
+#: authentication/views/feishu.py:145
msgid "Binding FeiShu successfully"
msgstr "绑定 飞书 成功"
-#: authentication/views/feishu.py:198
+#: authentication/views/feishu.py:196
msgid "Failed to get user from FeiShu"
msgstr "从飞书获取用户失败"
-#: authentication/views/feishu.py:204
+#: authentication/views/feishu.py:202
msgid "FeiShu is not bound"
msgstr "没有绑定飞书"
-#: authentication/views/feishu.py:205
+#: authentication/views/feishu.py:203
msgid "Please login with a password and then bind the FeiShu"
msgstr "请使用密码登录,然后绑定飞书"
-#: authentication/views/feishu.py:247 authentication/views/feishu.py:248
-msgid "Binding FeiShu failed"
-msgstr "绑定飞书失败"
-
#: authentication/views/login.py:82
msgid "Redirecting"
msgstr "跳转中"
@@ -1972,11 +2012,7 @@ msgstr "设置你的浏览器支持cookie"
msgid "FeiShu"
msgstr "飞书"
-#: authentication/views/login.py:202 authentication/views/mfa.py:50
-msgid "SMS"
-msgstr "短信"
-
-#: authentication/views/login.py:291
+#: authentication/views/login.py:269
msgid ""
"Wait for {} confirm, You also can copy link to her/him
\n"
" Don't close this page"
@@ -1984,59 +2020,54 @@ msgstr ""
"等待 {} 确认, 你也可以复制链接发给他/她
\n"
" 不要关闭本页面"
-#: authentication/views/login.py:296
+#: authentication/views/login.py:274
msgid "No ticket found"
msgstr "没有发现工单"
-#: authentication/views/login.py:328
+#: authentication/views/login.py:306
msgid "Logout success"
msgstr "退出登录成功"
-#: authentication/views/login.py:329
+#: authentication/views/login.py:307
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
-#: authentication/views/wecom.py:41
+#: authentication/views/wecom.py:37
msgid "WeCom Error, Please contact your system administrator"
msgstr "企业微信错误,请联系系统管理员"
-#: authentication/views/wecom.py:44
+#: authentication/views/wecom.py:40
msgid "WeCom Error"
msgstr "企业微信错误"
-#: authentication/views/wecom.py:91
+#: authentication/views/wecom.py:89
msgid "WeCom is already bound"
msgstr "企业微信已经绑定"
-#: authentication/views/wecom.py:143
+#: authentication/views/wecom.py:141
msgid "WeCom query user failed"
msgstr "企业微信查询用户失败"
-#: authentication/views/wecom.py:152
+#: authentication/views/wecom.py:150
msgid "The WeCom is already bound to another user"
msgstr "该企业微信已经绑定其他用户"
-#: authentication/views/wecom.py:157 authentication/views/wecom.py:240
-#: authentication/views/wecom.py:241
+#: authentication/views/wecom.py:155
msgid "Binding WeCom successfully"
msgstr "绑定 企业微信 成功"
-#: authentication/views/wecom.py:209
+#: authentication/views/wecom.py:204
msgid "Failed to get user from WeCom"
msgstr "从企业微信获取用户失败"
-#: authentication/views/wecom.py:215
+#: authentication/views/wecom.py:210
msgid "WeCom is not bound"
msgstr "没有绑定企业微信"
-#: authentication/views/wecom.py:216
+#: authentication/views/wecom.py:211
msgid "Please login with a password and then bind the WeCom"
msgstr "请使用密码登录,然后绑定企业微信"
-#: authentication/views/wecom.py:258 authentication/views/wecom.py:259
-msgid "Binding WeCom failed"
-msgstr "绑定企业微信失败"
-
#: common/const/__init__.py:6
#, python-format
msgid "%(name)s was created successfully"
@@ -2160,23 +2191,27 @@ msgstr "忽略的"
msgid "discard time"
msgstr "忽略时间"
-#: common/utils/ipip/utils.py:15
+#: common/utils/geoip/utils.py:17 common/utils/geoip/utils.py:30
msgid "Invalid ip"
msgstr "无效IP"
-#: common/validators.py:15
-msgid "Special char not allowed"
-msgstr "不能包含特殊字符"
+#: common/utils/geoip/utils.py:28
+msgid "LAN"
+msgstr ""
-#: common/validators.py:27
+#: common/utils/geoip/utils.py:35 common/utils/geoip/utils.py:45
+msgid "Unknown ip"
+msgstr "未知ip"
+
+#: common/validators.py:30
msgid "This field must be unique."
msgstr "字段必须唯一"
-#: common/validators.py:35
+#: common/validators.py:38
msgid "Should not contains special characters"
msgstr "不能包含特殊字符"
-#: common/validators.py:41
+#: common/validators.py:44
msgid "The mobile phone number format is incorrect"
msgstr "手机号格式不正确"
@@ -2220,13 +2255,12 @@ msgstr "邮件"
msgid "Site message"
msgstr "站内信"
-#: notifications/notifications.py:126
-msgid ""
-"\n"
-"Time: {}"
-msgstr ""
-"\n"
-"时间:{}"
+#: notifications/notifications.py:147 ops/models/adhoc.py:246
+#: xpack/plugins/change_auth_plan/models/base.py:108
+#: xpack/plugins/change_auth_plan/models/base.py:190
+#: xpack/plugins/gathered_user/models.py:79
+msgid "Time"
+msgstr "时间"
#: ops/api/celery.py:61 ops/api/celery.py:76
msgid "Waiting task start"
@@ -2329,12 +2363,6 @@ msgstr "开始时间"
msgid "End time"
msgstr "完成时间"
-#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models/base.py:108
-#: xpack/plugins/change_auth_plan/models/base.py:190
-#: xpack/plugins/gathered_user/models.py:79
-msgid "Time"
-msgstr "时间"
-
#: ops/models/adhoc.py:247 ops/models/command.py:28
#: terminal/serializers/session.py:39
msgid "Is finished"
@@ -2368,25 +2396,25 @@ msgstr "任务结束"
msgid "Server performance"
msgstr "监控告警"
-#: ops/notifications.py:47
+#: ops/notifications.py:54
#, python-brace-format
msgid "The terminal is offline: {name}"
msgstr "终端已离线: {name}"
-#: ops/notifications.py:53
-#, python-brace-format
-msgid "[Disk] Disk used more than {max_threshold}%: => {value} ({name})"
-msgstr "[Disk] 硬盘使用率超过 {max_threshold}%: => {value} ({name})"
-
#: ops/notifications.py:60
#, python-brace-format
-msgid "[Memory] Memory used more than {max_threshold}%: => {value} ({name})"
-msgstr "[Memory] 内存使用率超过 {max_threshold}%: => {value} ({name})"
+msgid "Disk used more than {max_threshold}%: => {value} ({name})"
+msgstr "硬盘使用率超过 {max_threshold}%: => {value} ({name})"
#: ops/notifications.py:67
#, python-brace-format
-msgid "[CPU] CPU load more than {max_threshold}: => {value} ({name})"
-msgstr "[CPU] CPU 使用率超过 {max_threshold}: => {value} ({name})"
+msgid "Memory used more than {max_threshold}%: => {value} ({name})"
+msgstr "内存使用率超过 {max_threshold}%: => {value} ({name})"
+
+#: ops/notifications.py:74
+#, python-brace-format
+msgid "CPU load more than {max_threshold}: => {value} ({name})"
+msgstr "CPU 使用率超过 {max_threshold}: => {value} ({name})"
#: ops/tasks.py:71
msgid "Clean task history period"
@@ -2514,271 +2542,37 @@ msgstr "失效日期"
msgid "From ticket"
msgstr "来自工单"
-#: perms/notifications.py:18 perms/notifications.py:40
-#, fuzzy
-#| msgid "Asset number"
-msgid "Assets may expire"
-msgstr "资产编号"
-
#: perms/notifications.py:21
-#, fuzzy, python-format
-#| msgid ""
-#| "\n"
-#| " Hello %(name)s:\n"
-#| "
\n"
-#| " Your account will expire in %(date_expired)s,\n"
-#| "
\n"
-#| " In order not to affect your normal work, please contact the "
-#| "administrator for confirmation.\n"
-#| "
\n"
-#| " "
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" Your permissions for the following assets may expire in three "
-"days:\n"
-"
\n"
-" %(assets)s\n"
-"
\n"
-" Please contact the administrator\n"
-" "
-msgstr ""
-"\n"
-" 您好 %(name)s:\n"
-"
\n"
-" 您的账户会在 %(date_expired)s 过期,\n"
-"
\n"
-" 为了不影响您正常工作,请联系管理员确认。\n"
-"
\n"
-" "
+msgid "You permed assets is about to expire"
+msgstr "你授权的资产即将到期"
-#: perms/notifications.py:43
-#, python-format
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"\n"
-"Your permissions for the following assets may expire in three days:\n"
-"\n"
-"\n"
-"%(assets)s\n"
-"\n"
-"\n"
-"Please contact the administrator\n"
-" "
-msgstr ""
+#: perms/notifications.py:25
+msgid "permed assets"
+msgstr "授权的资产"
-#: perms/notifications.py:69 perms/notifications.py:90
-#: perms/notifications.py:117 perms/notifications.py:140
-#, fuzzy
-#| msgid "Asset permission"
-msgid "Asset permission will expired"
-msgstr "资产授权"
+#: perms/notifications.py:63
+msgid "Asset permissions is about to expire"
+msgstr "资产授权规则将要过期"
-#: perms/notifications.py:72
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" The following asset permissions of organization %(org) will "
-"expire in three days\n"
-"
\n"
-" %(perms)s\n"
-" "
-msgstr ""
+#: perms/notifications.py:67
+msgid "asset permissions of organization {}"
+msgstr "组织 ({}) 的资产授权"
#: perms/notifications.py:93
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"\n"
-"The following asset permissions of organization %(org) will expire in three "
-"days\n"
-"\n"
-"\n"
-"%(perms)s\n"
-" "
-msgstr ""
+msgid "Your permed applications is about to expire"
+msgstr "你授权的应用即将过期"
-#: perms/notifications.py:123
-#, fuzzy, python-format
-#| msgid ""
-#| "\n"
-#| " Hello %(name)s:\n"
-#| "
\n"
-#| " Your account will expire in %(date_expired)s,\n"
-#| "
\n"
-#| " In order not to affect your normal work, please contact the "
-#| "administrator for confirmation.\n"
-#| "
\n"
-#| " "
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" The following asset permissions will expire in three days\n"
-"
\n"
-" %(content)s\n"
-" "
-msgstr ""
-"\n"
-" 您好 %(name)s:\n"
-"
\n"
-" 您的账户会在 %(date_expired)s 过期,\n"
-"
\n"
-" 为了不影响您正常工作,请联系管理员确认。\n"
-"
\n"
-" "
+#: perms/notifications.py:96
+msgid "permed applications"
+msgstr "授权的应用"
-#: perms/notifications.py:146
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"\n"
-"The following asset permissions of organization %(org) will expire in three "
-"days\n"
-"\n"
-"\n"
-"%(content)s\n"
-" "
-msgstr ""
+#: perms/notifications.py:134
+msgid "Application permissions is about to expire"
+msgstr "应用授权规则即将过期"
-#: perms/notifications.py:169 perms/notifications.py:191
-#, fuzzy
-#| msgid "Applications"
-msgid "Applications may expire"
-msgstr "应用管理"
-
-#: perms/notifications.py:172
-#, fuzzy, python-format
-#| msgid ""
-#| "\n"
-#| " Hello %(name)s:\n"
-#| "
\n"
-#| " Your account will expire in %(date_expired)s,\n"
-#| "
\n"
-#| " In order not to affect your normal work, please contact the "
-#| "administrator for confirmation.\n"
-#| "
\n"
-#| " "
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" Your permissions for the following applications may expire in "
-"three days:\n"
-"
\n"
-" %(apps)s\n"
-"
\n"
-" Please contact the administrator\n"
-" "
-msgstr ""
-"\n"
-" 您好 %(name)s:\n"
-"
\n"
-" 您的账户会在 %(date_expired)s 过期,\n"
-"
\n"
-" 为了不影响您正常工作,请联系管理员确认。\n"
-"
\n"
-" "
-
-#: perms/notifications.py:194
-#, python-format
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"\n"
-"Your permissions for the following applications may expire in three days:\n"
-"\n"
-"\n"
-"%(apps)s\n"
-"\n"
-"\n"
-"Please contact the administrator\n"
-" "
-msgstr ""
-
-#: perms/notifications.py:220 perms/notifications.py:241
-#: perms/notifications.py:268 perms/notifications.py:291
-#, fuzzy
-#| msgid "Application permission"
-msgid "Application permission will expired"
-msgstr "应用管理"
-
-#: perms/notifications.py:223
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" The following application permissions of organization %(org) "
-"will expire in three days\n"
-"
\n"
-" %(perms)s\n"
-" "
-msgstr ""
-
-#: perms/notifications.py:244
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"\n"
-"The following application permissions of organization %(org) will expire in "
-"three days\n"
-"\n"
-"\n"
-"%(perms)s\n"
-" "
-msgstr ""
-
-#: perms/notifications.py:274
-#, fuzzy, python-format
-#| msgid ""
-#| "\n"
-#| " Hello %(name)s:\n"
-#| "
\n"
-#| " Your account will expire in %(date_expired)s,\n"
-#| "
\n"
-#| " In order not to affect your normal work, please contact the "
-#| "administrator for confirmation.\n"
-#| "
\n"
-#| " "
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" The following application permissions will expire in three days\n"
-"
\n"
-" %(content)s\n"
-" "
-msgstr ""
-"\n"
-" 您好 %(name)s:\n"
-"
\n"
-" 您的账户会在 %(date_expired)s 过期,\n"
-"
\n"
-" 为了不影响您正常工作,请联系管理员确认。\n"
-"
\n"
-" "
-
-#: perms/notifications.py:297
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"\n"
-"The following application permissions of organization %(org) will expire in "
-"three days\n"
-"\n"
-"\n"
-"%(content)s\n"
-" "
-msgstr ""
+#: perms/notifications.py:137
+msgid "application permissions of organization {}"
+msgstr "组织 ({}) 的应用授权"
#: perms/serializers/application/permission.py:18
#: perms/serializers/application/permission.py:38
@@ -2836,11 +2630,27 @@ msgstr "节点名称"
msgid "System users display"
msgstr "系统用户名称"
+#: perms/templates/perms/_msg_item_permissions_expire.html:7
+#: perms/templates/perms/_msg_permed_items_expire.html:7
+#, python-format
+msgid ""
+"\n"
+" The following %(item_type)s will expire in 3 days\n"
+" "
+msgstr ""
+"\n"
+" 以下 %(item_type)s 即将在 3 天后过期\n"
+" "
+
+#: perms/templates/perms/_msg_permed_items_expire.html:22
+msgid "If you have any question, please contact the administrator"
+msgstr "如果有疑问或需求,请联系系统管理员"
+
#: settings/api/alibaba_sms.py:30 settings/api/tencent_sms.py:34
msgid "test_phone is required"
msgstr "测试手机号 该字段是必填项。"
-#: settings/api/alibaba_sms.py:51 settings/api/dingtalk.py:36
+#: settings/api/alibaba_sms.py:51 settings/api/dingtalk.py:31
#: settings/api/feishu.py:35 settings/api/tencent_sms.py:56
#: settings/api/wecom.py:36
msgid "Test success"
@@ -3303,7 +3113,7 @@ msgstr "邮件的敬语"
#: settings/serializers/email.py:55
msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)"
-msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)"
+msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 你好)"
#: settings/serializers/email.py:59
msgid "Create user email content"
@@ -3501,12 +3311,12 @@ msgid ""
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
#: settings/serializers/security.py:114
-msgid "Replay watermark"
-msgstr "录像水印"
+msgid "Enable watermark"
+msgstr "开启水印"
#: settings/serializers/security.py:115
-msgid "Enabled, the session replay contains watermark information"
-msgstr "启用后,会话录像将包含水印信息"
+msgid "Enabled, the web session and replay contains watermark information"
+msgstr "启用后,Web 会话和录像将包含水印信息"
#: settings/serializers/security.py:119
msgid "Connection max idle time"
@@ -3928,7 +3738,7 @@ msgstr "数据库应用"
msgid "Perms"
msgstr "权限管理"
-#: templates/_nav.html:97 terminal/notifications.py:20
+#: templates/_nav.html:97 terminal/notifications.py:22
msgid "Sessions"
msgstr "会话管理"
@@ -4038,11 +3848,11 @@ msgstr "确认删除"
msgid "Are you sure delete"
msgstr "您确定删除吗?"
-#: templates/flash_message_standalone.html:28
+#: templates/flash_message_standalone.html:25
msgid "Cancel"
msgstr "取消"
-#: templates/flash_message_standalone.html:37
+#: templates/flash_message_standalone.html:34
msgid "Go"
msgstr "立即"
@@ -4271,6 +4081,7 @@ msgstr "输出"
#: terminal/backends/command/models.py:23 terminal/models/sharing.py:15
#: terminal/models/sharing.py:58
+#: terminal/templates/terminal/_msg_command_alert.html:16
msgid "Session"
msgstr "会话"
@@ -4437,109 +4248,14 @@ msgstr "命令存储"
msgid "Replay storage"
msgstr "录像存储"
-#: terminal/notifications.py:66
+#: terminal/notifications.py:68
msgid "Danger command alert"
msgstr "危险命令告警"
-#: terminal/notifications.py:82
-#, python-format
-msgid ""
-"\n"
-"Command: %(command)s\n"
-"Asset: %(hostname)s (%(host_ip)s)\n"
-"User: %(user)s\n"
-"Level: %(risk_level)s\n"
-"Session: %(session_detail_url)s?oid=%(oid)s\n"
-" "
-msgstr ""
-"\n"
-"命令: %(command)s\n"
-"资产: %(hostname)s (%(host_ip)s)\n"
-"用户: %(user)s\n"
-"等级: %(risk_level)s\n"
-"会话: %(session_detail_url)s?oid=%(oid)s\n"
-" "
-
-#: terminal/notifications.py:113
-#, python-format
-msgid ""
-"\n"
-" Command: %(command)s\n"
-"
\n"
-" Asset: %(hostname)s (%(host_ip)s)\n"
-"
\n"
-" User: %(user)s\n"
-"
\n"
-" Level: %(risk_level)s\n"
-"
\n"
-" Session: session "
-"detail\n"
-"
\n"
-" "
-msgstr ""
-"\n"
-" 命令: %(command)s\n"
-"
\n"
-" 资产: %(hostname)s (%(host_ip)s)\n"
-"
\n"
-" 用户: %(user)s\n"
-"
\n"
-" 等级: %(risk_level)s\n"
-"
\n"
-" 会话: 会话详情\n"
-"
\n"
-" "
-
-#: terminal/notifications.py:142
+#: terminal/notifications.py:106
msgid "Batch danger command alert"
msgstr "批量危险命令告警"
-#: terminal/notifications.py:153
-#, python-format
-msgid ""
-"\n"
-" Assets: %(assets)s\n"
-"
\n"
-" User: %(user)s\n"
-"
\n"
-" Level: %(risk_level)s\n"
-"
\n"
-"\n"
-" ----------------- Commands ---------------- "
-"
\n"
-" %(command)s
\n"
-" ----------------- Commands ---------------- "
-"
\n"
-" "
-msgstr ""
-"\n"
-" 资产: %(assets)s\n"
-"
\n"
-" 用户: %(user)s\n"
-"
\n"
-" 等级: %(risk_level)s\n"
-"
\n"
-"
\n"
-" ----------------- 命令 ----------------
\n"
-" %(command)s
\n"
-" ----------------- 命令 ----------------
\n"
-"
\n"
-" "
-
-#: terminal/notifications.py:180
-#, python-format
-msgid ""
-"\n"
-"Assets: %(assets)s\n"
-"User: %(user)s\n"
-"Level: %(risk_level)s\n"
-"\n"
-"Commands 👇 ------------\n"
-"%(command)s\n"
-"------------------------\n"
-" "
-msgstr ""
-
#: terminal/serializers/session.py:31
msgid "User ID"
msgstr "用户 ID"
@@ -4641,6 +4357,15 @@ msgstr "忽略证书认证"
msgid "Not found"
msgstr "没有发现"
+#: terminal/templates/terminal/_msg_command_alert.html:13
+#: terminal/templates/terminal/_msg_command_execue_alert.html:7
+msgid "Level"
+msgstr "级别"
+
+#: terminal/templates/terminal/_msg_command_alert.html:16
+msgid "view"
+msgstr "查看"
+
#: tickets/const.py:8
msgid "General"
msgstr "一般"
@@ -4893,15 +4618,19 @@ msgstr "流程"
msgid "TicketFlow"
msgstr "工单流程"
-#: tickets/notifications.py:57
-msgid "click here to review"
-msgstr "点击查看"
+#: tickets/notifications.py:46
+msgid "New Ticket - {} ({})"
+msgstr "新工单 - {} ({})"
-#: tickets/notifications.py:75
+#: tickets/notifications.py:54
msgid "Your has a new ticket, applicant - {}"
msgstr "你有一个新的工单, 申请人 - {}"
-#: tickets/notifications.py:88
+#: tickets/notifications.py:68
+msgid "Ticket has processed - {} ({})"
+msgstr "你的工单已被处理, 处理人 - {} ({})"
+
+#: tickets/notifications.py:77
msgid "Your ticket has been processed, processor - {}"
msgstr "你的工单已被处理, 处理人 - {}"
@@ -5021,6 +4750,12 @@ msgstr "请选择受理人"
msgid "The current organization type already exists"
msgstr "当前组织已存在该类型"
+#: tickets/templates/tickets/_msg_ticket.html:11
+#, fuzzy
+#| msgid "click here to review"
+msgid "Click here to review"
+msgstr "点击查看"
+
#: users/api/user.py:208
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
@@ -5177,462 +4912,33 @@ msgstr "管理员"
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
-#: users/notifications.py:19 users/notifications.py:52
+#: users/notifications.py:20
+#: users/templates/users/_msg_password_expire_reminder.html:17
#: users/templates/users/reset_password.html:5
#: users/templates/users/reset_password.html:6
msgid "Reset password"
msgstr "重置密码"
-#: users/notifications.py:20
-#, python-format
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"Please click the link below to reset your password, if not your request, "
-"concern your account security\n"
-"\n"
-"Click here reset password 👇\n"
-"%(rest_password_url)s?token=%(rest_password_token)s\n"
-"\n"
-"This link is valid for 1 hour. After it expires, \n"
-"\n"
-"request new one 👇\n"
-"%(forget_password_url)s?email=%(email)s\n"
-"\n"
-"-------------------\n"
-"\n"
-"Login direct 👇\n"
-"%(login_url)s\n"
-"\n"
-msgstr ""
-"\n"
-"您好 %(name)s:\n"
-"请点击下面链接重置密码, 如果不是您申请的,请关注账号安全\n"
-" \n"
-"请点击这里设置密码 👇\n"
-"%(rest_password_url)s?token=%(rest_password_token)s\n"
-"\n"
-"这个链接有效期1小时, 超过时间您可以, \n"
-"\n"
-"重新申请 👇\n"
-"%(forget_password_url)s?email=%(email)s\n"
-"\n"
-"-------------------\n"
-"\n"
-"直接登录 👇\n"
-"%(login_url)s\n"
-"\n"
-
-#: users/notifications.py:53
-#, python-format
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" Please click the link below to reset your password, if not your "
-"request, concern your account security\n"
-"
\n"
-" Click here reset password\n"
-"
\n"
-" This link is valid for 1 hour. After it expires, request new one\n"
-" \n"
-"
\n"
-" ---\n"
-" \n"
-"
\n"
-" Login direct\n"
-" \n"
-"
\n"
-" "
-msgstr ""
-"\n"
-" 您好 %(name)s:\n"
-"
\n"
-" 请点击下面链接重置密码, 如果不是您申请的,请关注账号安全\n"
-"
\n"
-" 请点击这"
-"里设置密码 \n"
-"
\n"
-" 这个链接有效期1小时, 超过时间您可以重新申请\n"
-"\n"
-"
\n"
-" ---\n"
-"\n"
-"
\n"
-" 直接登录\n"
-"\n"
-"
\n"
-" "
-
-#: users/notifications.py:92 users/notifications.py:124
-#: users/views/profile/reset.py:127
+#: users/notifications.py:50 users/views/profile/reset.py:127
msgid "Reset password success"
msgstr "重置密码成功"
-#: users/notifications.py:93
-#, python-format
-msgid ""
-"\n"
-" \n"
-"Hi %(name)s:\n"
-"\n"
-"Your JumpServer password has just been successfully updated.\n"
-"\n"
-"If the password update was not initiated by you, your account may have "
-"security issues. \n"
-"It is recommended that you log on to the JumpServer immediately and change "
-"your password.\n"
-"\n"
-"If you have any questions, you can contact the administrator.\n"
-"\n"
-"-------------------\n"
-"\n"
-"\n"
-"IP Address: %(ip_address)s\n"
-"\n"
-"\n"
-"Browser: %(browser)s\n"
-" \n"
-" "
-msgstr ""
-"\n"
-" \n"
-"Hi %(name)s:\n"
-"\n"
-"你的 JumpServer 密码刚刚已经成功更新。\n"
-"\n"
-"如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题。 \n"
-"建议你立刻登录 JumpServer 更改密码。 \n"
-"\n"
-"如果你有任何疑问,可以联系管理员。\n"
-"\n"
-"-------------------\n"
-"\n"
-"\n"
-"IP 地址: %(ip_address)s\n"
-"\n"
-"浏览器: %(browser)s\n"
-" \n"
-" "
+#: users/notifications.py:78
+msgid "Password is about expire"
+msgstr "密码即将过期"
-#: users/notifications.py:125
-#, python-format
-msgid ""
-"\n"
-" \n"
-" Hi %(name)s:\n"
-"
\n"
-" \n"
-" \n"
-"
\n"
-" Your JumpServer password has just been successfully updated.\n"
-"
\n"
-" \n"
-"
\n"
-" If the password update was not initiated by you, your account may "
-"have security issues. \n"
-" It is recommended that you log on to the JumpServer immediately and "
-"change your password.\n"
-"
\n"
-"\n"
-"
\n"
-" If you have any questions, you can contact the administrator.\n"
-"
\n"
-"
\n"
-" ---\n"
-"
\n"
-"
\n"
-" IP Address: %(ip_address)s\n"
-"
\n"
-"
\n"
-" Browser: %(browser)s\n"
-"
\n"
-" \n"
-" "
-msgstr ""
-"\n"
-" \n"
-" Hi %(name)s:\n"
-"
\n"
-" \n"
-" \n"
-"
\n"
-" 你的 JumpServer 密码刚刚已经成功更新。
\n"
-" \n"
-"
\n"
-" 如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题。\n"
-" 建议你立刻登录 JumpServer 更改密码。\n"
-"
\n"
-" \n"
-"
\n"
-" 如果你有任何疑问,可以联系管理员。
\n"
-"
\n"
-" ---\n"
-"
\n"
-"
\n"
-" IP 地址: %(ip_address)s\n"
-"
\n"
-"
\n"
-" 浏览器: %(browser)s\n"
-"
\n"
-" \n"
-" "
+#: users/notifications.py:105
+msgid "Account is about expire"
+msgstr "账号即将过期"
-#: users/notifications.py:170 users/notifications.py:206
-msgid "Security notice"
-msgstr "安全通知"
+#: users/notifications.py:127
+msgid "Reset SSH Key"
+msgstr "重置 SSH 密钥"
-#: users/notifications.py:171
-#, python-format
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"Your password will expire in %(date_password_expired)s,\n"
-"\n"
-"For your account security, please click on the link below to update your "
-"password in time\n"
-"\n"
-"Click here update password 👇\n"
-"%(update_password_url)s\n"
-"\n"
-"If your password has expired, please click 👇 to apply for a password reset "
-"email.\n"
-"%(forget_password_url)s?email=%(email)s\n"
-"\n"
-"-------------------\n"
-"\n"
-"Login direct 👇\n"
-"%(login_url)s\n"
-"\n"
-" "
-msgstr ""
-"\n"
-"您好 %(name)s:\n"
-"\n"
-"您的密码会在 %(date_password_expired)s 过期,\n"
-"\n"
-"为了您的账号安全,请点击下面的链接及时更新密码 \n"
-"\n"
-"请点击这里更新密码 👇\n"
-"%(update_password_url)s\n"
-"如果您的密码已经过期,请点击 👇 申请一份重置密码邮件。 \n"
-"%(forget_password_url)s?email=%(email)s\n"
-"\n"
-"-------------------\n"
-"\n"
-"直接登录 👇\n"
-"%(login_url)s\n"
-"\n"
-" "
-
-#: users/notifications.py:207
-#, python-format
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" Your password will expire in %(date_password_expired)s,\n"
-"
\n"
-" For your account security, please click on the link below to update "
-"your password in time\n"
-"
\n"
-" Click here update password\n"
-"
\n"
-" If your password has expired, please click \n"
-" Password "
-"expired \n"
-" to apply for a password reset email.\n"
-" \n"
-"
\n"
-" ---\n"
-" \n"
-"
\n"
-" Login direct\n"
-" \n"
-"
\n"
-" "
-msgstr ""
-"\n"
-" 您好 %(name)s:\n"
-"
\n"
-" 您的密码会在 %(date_password_expired)s 过期,\n"
-"
\n"
-" 为了您的账号安全,请点击下面的链接及时更新密码\n"
-"
\n"
-" 请点击这里更新密码\n"
-"
\n"
-" 如果您的密码已经过期,请点击 \n"
-" 密码过期 \n"
-" 申请一份重置密码邮件。\n"
-"\n"
-"
\n"
-" ---\n"
-"\n"
-"
\n"
-" 直接登录\n"
-"\n"
-"
\n"
-" "
-
-#: users/notifications.py:244 users/notifications.py:263
-msgid "Expiration notice"
-msgstr "过期通知"
-
-#: users/notifications.py:245
-#, python-format
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"Your account will expire in %(date_expired)s,\n"
-"\n"
-"In order not to affect your normal work, please contact the administrator "
-"for confirmation.\n"
-"\n"
-" "
-msgstr ""
-"\n"
-"您好 %(name)s:\n"
-"\n"
-"您的账户会在 %(date_expired)s 过期,\n"
-"\n"
-"为了不影响您正常工作,请联系管理员确认。\n"
-" "
-
-#: users/notifications.py:264
-#, python-format
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" Your account will expire in %(date_expired)s,\n"
-"
\n"
-" In order not to affect your normal work, please contact the "
-"administrator for confirmation.\n"
-"
\n"
-" "
-msgstr ""
-"\n"
-" 您好 %(name)s:\n"
-"
\n"
-" 您的账户会在 %(date_expired)s 过期,\n"
-"
\n"
-" 为了不影响您正常工作,请联系管理员确认。\n"
-"
\n"
-" "
-
-#: users/notifications.py:284 users/notifications.py:305
-msgid "SSH Key Reset"
-msgstr "重置SSH密钥"
-
-#: users/notifications.py:285
-#, python-format
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"Your ssh public key has been reset by site administrator.\n"
-"Please login and reset your ssh public key.\n"
-"\n"
-"Login direct 👇\n"
-"%(login_url)s\n"
-"\n"
-" "
-msgstr ""
-"\n"
-"你好 %(name)s:\n"
-"\n"
-"您的密钥已被管理员重置,\n"
-"请登录并重新设置您的密钥.\n"
-"\n"
-"直接登录 👇\n"
-"%(login_url)s\n"
-"\n"
-" "
-
-#: users/notifications.py:306
-#, python-format
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" Your ssh public key has been reset by site administrator.\n"
-" Please login and reset your ssh public key.\n"
-"
\n"
-" Login direct\n"
-" \n"
-"
\n"
-" "
-msgstr ""
-"\n"
-" 你好 %(name)s:\n"
-"
\n"
-" 您的密钥已被管理员重置,\n"
-" 请登录并重新设置您的密钥.\n"
-"
\n"
-" 直接登录\n"
-"\n"
-"
\n"
-" "
-
-#: users/notifications.py:328 users/notifications.py:348
-msgid "MFA Reset"
+#: users/notifications.py:147
+msgid "Reset MFA"
msgstr "重置 MFA"
-#: users/notifications.py:329
-#, python-format
-msgid ""
-"\n"
-"Hello %(name)s:\n"
-"\n"
-"Your MFA has been reset by site administrator.\n"
-"Please login and reset your MFA.\n"
-"\n"
-"Login direct 👇 \n"
-"%(login_url)s\n"
-"\n"
-" "
-msgstr ""
-"\n"
-"你好 %(name)s:\n"
-"\n"
-"您的 MFA 已被管理员重置,\n"
-"请登录并重新设置您的 MFA.\n"
-"\n"
-"直接登录 👇 \n"
-"%(login_url)s\n"
-"\n"
-" "
-
-#: users/notifications.py:349
-#, python-format
-msgid ""
-"\n"
-" Hello %(name)s:\n"
-"
\n"
-" Your MFA has been reset by site administrator.\n"
-" Please login and reset your MFA.\n"
-"
\n"
-" Login direct\n"
-" \n"
-"
\n"
-" "
-msgstr ""
-"\n"
-" 你好 %(name)s:\n"
-"
\n"
-" 您的 MFA 已被管理员重置,\n"
-" 请登录并重新设置您的 MFA.\n"
-"
\n"
-" 直接登录\n"
-"\n"
-"
\n"
-" "
-
#: users/serializers/profile.py:29
msgid "The old password is incorrect"
msgstr "旧密码错误"
@@ -5751,6 +5057,56 @@ msgstr "的密码完成绑定操作"
msgid "Loading"
msgstr "加载中"
+#: users/templates/users/_msg_account_expire_reminder.html:7
+msgid "Your account will expire in"
+msgstr "您的账号即将过期"
+
+#: users/templates/users/_msg_account_expire_reminder.html:8
+msgid ""
+"In order not to affect your normal work, please contact the administrator "
+"for confirmation."
+msgstr ""
+"为了不影响您正常工作,请联系管理员确认。\n"
+" "
+
+#: users/templates/users/_msg_password_expire_reminder.html:7
+msgid "Your password will expire in"
+msgstr "您的密码将过期"
+
+#: users/templates/users/_msg_password_expire_reminder.html:8
+msgid ""
+"For your account security, please click on the link below to update your "
+"password in time"
+msgstr "为了您的账号安全,请点击下面的链接及时更新密码"
+
+#: users/templates/users/_msg_password_expire_reminder.html:11
+msgid "Click here update password"
+msgstr "点击这里更新密码"
+
+#: users/templates/users/_msg_password_expire_reminder.html:16
+msgid "If your password has expired, please click"
+msgstr "如果你的密码已过期,先点击"
+
+#: users/templates/users/_msg_password_expire_reminder.html:18
+msgid "to apply for a password reset email."
+msgstr "申请重置"
+
+#: users/templates/users/_msg_reset_mfa.html:7
+msgid "Your MFA has been reset by site administrator."
+msgstr "你的 MFA 已经被管理员重置。"
+
+#: users/templates/users/_msg_reset_mfa.html:8
+msgid "Please login and reset your MFA."
+msgstr "请登录并重新设置你的 MFA"
+
+#: users/templates/users/_msg_reset_ssh_key.html:7
+msgid "Your ssh public key has been reset by site administrator."
+msgstr "你的 SSH 密钥已经被管理员重置。"
+
+#: users/templates/users/_msg_reset_ssh_key.html:8
+msgid "Please login and reset your ssh public key."
+msgstr "请登录并重新设置你的密钥"
+
#: users/templates/users/_select_user_modal.html:5
msgid "Please Select User"
msgstr "选择用户"
@@ -5982,7 +5338,7 @@ msgstr "创建账户成功"
#: users/utils.py:61
#, python-format
msgid "Hello %(name)s"
-msgstr "您好 %(name)s"
+msgstr "你好 %(name)s"
#: users/views/profile/otp.py:122 users/views/profile/otp.py:161
#: users/views/profile/otp.py:181
@@ -6638,6 +5994,57 @@ msgstr "旗舰版"
msgid "Community edition"
msgstr "社区版"
+#~ msgid ""
+#~ "\n"
+#~ "Time: {}"
+#~ msgstr ""
+#~ "\n"
+#~ "时间:{}"
+
+#~ msgid "asset permission"
+#~ msgstr "资产授权"
+
+#~ msgid "Asset permissions will expired"
+#~ msgstr "资产授权即将过期"
+
+#, python-brace-format
+#~ msgid ""
+#~ "\n"
+#~ "Organization: {org}\n"
+#~ "Permissions: {perms}\n"
+#~ msgstr ""
+#~ "\n"
+#~ "组织: {org}\n"
+#~ "权限: {perms}\n"
+
+#~ msgid "asset permissions of organization"
+#~ msgstr "组织资产授权"
+
+#~ msgid "application permissions of organization"
+#~ msgstr "组织的应用授权 {}"
+
+#, python-brace-format
+#~ msgid ""
+#~ "\n"
+#~ " Organization: {org} \n"
+#~ " Permissions: {perms} \n"
+#~ msgstr ""
+#~ "\n"
+#~ "组织: {org} \n"
+#~ "授权: {perms} \n"
+
+#~ msgid "You've been hacked"
+#~ msgstr "你被攻击了"
+
+#~ msgid "Binding DingTalk failed"
+#~ msgstr "绑定钉钉失败"
+
+#~ msgid "Binding FeiShu failed"
+#~ msgstr "绑定飞书失败"
+
+#~ msgid "Binding WeCom failed"
+#~ msgstr "绑定企业微信失败"
+
#~ msgid "Enable Login MFA"
#~ msgstr "启用登录MFA"
diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py
index f14a8ebe9..e888822d4 100644
--- a/apps/notifications/apps.py
+++ b/apps/notifications/apps.py
@@ -6,4 +6,5 @@ class NotificationsConfig(AppConfig):
def ready(self):
from . import signals_handler
+ from . import notifications
super().ready()
diff --git a/apps/notifications/notifications.py b/apps/notifications/notifications.py
index d4456ac90..3bb8866c2 100644
--- a/apps/notifications/notifications.py
+++ b/apps/notifications/notifications.py
@@ -1,12 +1,12 @@
-from typing import Iterable
import traceback
+from html2text import HTML2Text
+from typing import Iterable
from itertools import chain
-import time
from celery import shared_task
from django.utils.translation import gettext_lazy as _
-from common.utils.timezone import now
+from common.utils.timezone import local_now
from common.utils import lazyproperty
from users.models import User
from notifications.backends import BACKEND
@@ -17,6 +17,7 @@ __all__ = ('SystemMessage', 'UserMessage', 'system_msgs')
system_msgs = []
user_msgs = []
+all_msgs = []
class MessageType(type):
@@ -58,6 +59,7 @@ class Message(metaclass=MessageType):
message_type_label: str
category: str
category_label: str
+ text_msg_ignore_links = True
@classmethod
def get_message_type(cls):
@@ -66,6 +68,10 @@ class Message(metaclass=MessageType):
def publish_async(self):
return publish_task.delay(self)
+ @classmethod
+ def gen_test_msg(cls):
+ raise NotImplementedError
+
def publish(self):
raise NotImplementedError
@@ -80,31 +86,46 @@ class Message(metaclass=MessageType):
continue
get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg)
-
try:
msg = get_msg_method()
except NotImplementedError:
continue
client = backend.client()
-
client.send_msg(users, **msg)
- except:
+ except Exception:
traceback.print_exc()
- def send_test_msg(self):
+ @classmethod
+ def send_test_msg(cls, ding=True):
+ msg = cls.gen_test_msg()
+ if not msg:
+ return
+
from users.models import User
users = User.objects.filter(username='admin')
- self.send_msg(users, [])
+ backends = []
+ if ding:
+ backends.append(BACKEND.DINGTALK)
+ msg.send_msg(users, backends)
- def get_common_msg(self) -> dict:
- raise NotImplementedError
-
- def get_text_msg(self) -> dict:
- return self.common_msg
+ @staticmethod
+ def get_common_msg() -> dict:
+ return {
+ 'subject': '',
+ 'message': ''
+ }
def get_html_msg(self) -> dict:
- return self.common_msg
+ return self.get_common_msg()
+
+ def get_text_msg(self) -> dict:
+ h = HTML2Text()
+ msg = self.get_html_msg()
+ content = msg['message']
+ h.ignore_links = self.text_msg_ignore_links
+ msg['message'] = h.handle(content)
+ return msg
@lazyproperty
def common_msg(self) -> dict:
@@ -123,7 +144,8 @@ class Message(metaclass=MessageType):
def get_dingtalk_msg(self) -> dict:
# 钉钉相同的消息一天只能发一次,所以给所有消息添加基于时间的序号,使他们不相同
message = self.text_msg['message']
- suffix = _('\nTime: {}').format(now())
+ time = local_now().strftime('%Y-%m-%d %H:%M:%S')
+ suffix = '\n{}: {}'.format(_('Time'), time)
return {
'subject': self.text_msg['subject'],
@@ -144,7 +166,25 @@ class Message(metaclass=MessageType):
def get_sms_msg(self) -> dict:
return self.text_msg
- # --------------------------------------------------------------
+
+ @classmethod
+ def test_all_messages(cls):
+ def get_subclasses(cls):
+ """returns all subclasses of argument, cls"""
+ if issubclass(cls, type):
+ subclasses = cls.__subclasses__(cls)
+ else:
+ subclasses = cls.__subclasses__()
+ for subclass in subclasses:
+ subclasses.extend(get_subclasses(subclass))
+ return subclasses
+
+ messages_cls = get_subclasses(cls)
+ for _cls in messages_cls:
+ try:
+ msg = _cls.send_test_msg()
+ except NotImplementedError:
+ continue
class SystemMessage(Message):
@@ -161,13 +201,16 @@ class SystemMessage(Message):
*subscription.users.all(),
*chain(*[g.users.all() for g in subscription.groups.all()])
]
-
self.send_msg(users, receive_backends)
@classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription):
pass
+ @classmethod
+ def gen_test_msg(cls):
+ raise NotImplementedError
+
class UserMessage(Message):
user: User
@@ -179,7 +222,9 @@ class UserMessage(Message):
"""
发送消息到每个用户配置的接收方式上
"""
-
sub = UserMsgSubscription.objects.get(user=self.user)
-
self.send_msg([self.user], sub.receive_backends)
+
+ @classmethod
+ def gen_test_msg(cls):
+ raise NotImplementedError
diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py
index 6e3f45f9d..7a3c9457f 100644
--- a/apps/notifications/site_msg.py
+++ b/apps/notifications/site_msg.py
@@ -1,7 +1,7 @@
from django.db.models import F
from django.db import transaction
-from common.utils.timezone import now
+from common.utils.timezone import local_now
from common.utils import get_logger
from users.models import User
from .models import SiteMessage as SiteMessageModel, SiteMessageUsers
@@ -88,7 +88,7 @@ class SiteMessageUtil:
for site_msg_user in site_msg_users:
site_msg_user.has_read = True
- site_msg_user.read_at = now()
+ site_msg_user.read_at = local_now()
SiteMessageUsers.objects.bulk_update(
site_msg_users, fields=('has_read', 'read_at'))
diff --git a/apps/ops/celery/utils.py b/apps/ops/celery/utils.py
index c35db0f0e..3c38ee287 100644
--- a/apps/ops/celery/utils.py
+++ b/apps/ops/celery/utils.py
@@ -11,7 +11,7 @@ from django_celery_beat.models import (
PeriodicTask, IntervalSchedule, CrontabSchedule, PeriodicTasks
)
-from common.utils.timezone import now
+from common.utils.timezone import local_now
from common.utils import get_logger
logger = get_logger(__name__)
@@ -52,7 +52,7 @@ def create_or_update_celery_periodic_tasks(tasks):
interval = IntervalSchedule.objects.filter(**kwargs).first()
if interval is None:
interval = IntervalSchedule.objects.create(**kwargs)
- last_run_at = now()
+ last_run_at = local_now()
elif isinstance(detail.get("crontab"), str):
try:
minute, hour, day, month, week = detail["crontab"].split()
diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py
index 18cb9bf61..7e0643dcc 100644
--- a/apps/ops/notifications.py
+++ b/apps/ops/notifications.py
@@ -24,13 +24,6 @@ class ServerPerformanceMessage(SystemMessage):
'message': self._msg
}
- def get_text_msg(self) -> dict:
- subject = self._msg[:80]
- return {
- 'subject': subject.replace('
', '; '),
- 'message': self._msg.replace('
', '\n')
- }
-
@classmethod
def post_insert_to_db(cls, subscription: SystemMsgSubscription):
admins = User.objects.filter(role=User.ROLE.ADMIN)
@@ -38,6 +31,20 @@ class ServerPerformanceMessage(SystemMessage):
subscription.receive_backends = [BACKEND.EMAIL]
subscription.save()
+ @classmethod
+ def gen_test_msg(cls):
+ alarm_messages = []
+ items_mapper = ServerPerformanceCheckUtil.items_mapper
+ for item, data in items_mapper.items():
+ msg = data['alarm_msg_format']
+ max_threshold = data['max_threshold']
+ value = 123
+ msg = msg.format(max_threshold=max_threshold, value=value, name='Fake terminal')
+ alarm_messages.append(msg)
+
+ msg = '
'.join(alarm_messages)
+ return cls(msg)
+
class ServerPerformanceCheckUtil(object):
items_mapper = {
@@ -50,21 +57,21 @@ class ServerPerformanceCheckUtil(object):
'default': 0,
'max_threshold': 80,
'alarm_msg_format': _(
- '[Disk] Disk used more than {max_threshold}%: => {value} ({name})'
+ 'Disk used more than {max_threshold}%: => {value} ({name})'
)
},
'memory_used': {
'default': 0,
'max_threshold': 85,
'alarm_msg_format': _(
- '[Memory] Memory used more than {max_threshold}%: => {value} ({name})'
+ 'Memory used more than {max_threshold}%: => {value} ({name})'
),
},
'cpu_load': {
'default': 0,
'max_threshold': 5,
'alarm_msg_format': _(
- '[CPU] CPU load more than {max_threshold}: => {value} ({name})'
+ 'CPU load more than {max_threshold}: => {value} ({name})'
),
},
}
diff --git a/apps/perms/apps.py b/apps/perms/apps.py
index 5bb7420bb..d6ce1f752 100644
--- a/apps/perms/apps.py
+++ b/apps/perms/apps.py
@@ -9,3 +9,4 @@ class PermsConfig(AppConfig):
def ready(self):
super().ready()
from . import signals_handler
+ from . import notifications
diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py
index 8d93369ac..a8319b92f 100644
--- a/apps/perms/notifications.py
+++ b/apps/perms/notifications.py
@@ -1,310 +1,155 @@
-from datetime import datetime
-from urllib.parse import urljoin
from django.utils.translation import ugettext as _
-from django.conf import settings
+from django.template.loader import render_to_string
-from common.utils import reverse, get_request_ip_or_data, get_request_user_agent, lazyproperty
-from notifications.notifications import UserMessage, SystemMessage
+from common.utils import reverse as js_reverse
+from notifications.notifications import UserMessage
-class AssetPermWillExpireMsg(UserMessage):
+class BasePermMsg(UserMessage):
+ @classmethod
+ def gen_test_msg(cls):
+ return
+
+
+class PermedWillExpireUserMsg(BasePermMsg):
def __init__(self, user, assets):
super().__init__(user)
self.assets = assets
def get_html_msg(self) -> dict:
- user = self.user
- subject = _('Assets may expire')
-
- assets_text = ','.join(str(asset) for asset in self.assets)
- message = _("""
- Hello %(name)s:
-
- Your permissions for the following assets may expire in three days:
-
- %(assets)s
-
- Please contact the administrator
- """) % {
- 'name': user.name,
- 'assets': assets_text
+ subject = _("You permed assets is about to expire")
+ context = {
+ 'name': self.user.name,
+ 'items': [str(asset) for asset in self.assets],
+ 'item_type': _("permed assets"),
+ 'show_help': True
}
+ message = render_to_string('perms/_msg_permed_items_expire.html', context)
return {
'subject': subject,
'message': message
}
- def get_text_msg(self) -> dict:
- user = self.user
- subject = _('Assets may expire')
- assets_text = ','.join(str(asset) for asset in self.assets)
-
- message = _("""
-Hello %(name)s:
-\n
-Your permissions for the following assets may expire in three days:
-\n
-%(assets)s
-\n
-Please contact the administrator
- """) % {
- 'name': user.name,
- 'assets': assets_text
- }
- return {
- 'subject': subject,
- 'message': message
- }
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ from assets.models import Asset
+ user = User.objects.first()
+ assets = Asset.objects.all()[:10]
+ return cls(user, assets)
-class AssetPermWillExpireForOrgAdminMsg(UserMessage):
+class AssetPermsWillExpireForOrgAdminMsg(BasePermMsg):
+
def __init__(self, user, perms, org):
super().__init__(user)
self.perms = perms
self.org = org
- def get_html_msg(self) -> dict:
- user = self.user
- subject = _('Asset permission will expired')
- perms_text = ','.join(str(perm) for perm in self.perms)
+ def get_items_with_url(self):
+ items_with_url = []
+ for perm in self.perms:
+ url = js_reverse(
+ 'perms:asset-permission-detail',
+ kwargs={'pk': perm.id}, external=True,
+ api_to_ui=True
+ ) + f'?oid={perm.org_id}'
+ items_with_url.append([perm.name, url])
+ return items_with_url
- message = _("""
- Hello %(name)s:
-
- The following asset permissions of organization %(org) will expire in three days
-
- %(perms)s
- """) % {
- 'name': user.name,
- 'org': self.org,
- 'perms': perms_text
+ def get_html_msg(self):
+ items_with_url = self.get_items_with_url()
+ subject = _("Asset permissions is about to expire")
+ context = {
+ 'name': self.user.name,
+ 'items_with_url': items_with_url,
+ 'item_type': _('asset permissions of organization {}').format(self.org)
}
+ message = render_to_string('perms/_msg_item_permissions_expire.html', context)
return {
'subject': subject,
'message': message
}
- def get_text_msg(self) -> dict:
- user = self.user
- subject = _('Asset permission will expired')
- perms_text = ','.join(str(perm) for perm in self.perms)
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ from perms.models import AssetPermission
+ from orgs.models import Organization
- message = _("""
-Hello %(name)s:
-\n
-The following asset permissions of organization %(org) will expire in three days
-\n
-%(perms)s
- """) % {
- 'name': user.name,
- 'org': self.org,
- 'perms': perms_text
- }
- return {
- 'subject': subject,
- 'message': message
- }
+ user = User.objects.first()
+ perms = AssetPermission.objects.all()[:10]
+ org = Organization.objects.first()
+ return cls(user, perms, org)
-class AssetPermWillExpireForAdminMsg(UserMessage):
- def __init__(self, user, org_perm_mapper: dict):
- super().__init__(user)
- self.org_perm_mapper = org_perm_mapper
-
- def get_html_msg(self) -> dict:
- user = self.user
- subject = _('Asset permission will expired')
-
- content = ''
- for org, perms in self.org_perm_mapper.items():
- content += f'
Orgnization: {org}
Permissions: {",".join(str(perm) for perm in perms)}
'
-
- message = _("""
- Hello %(name)s:
-
- The following asset permissions will expire in three days
-
- %(content)s
- """) % {
- 'name': user.name,
- 'content': content,
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
- def get_text_msg(self) -> dict:
- user = self.user
- subject = _('Asset permission will expired')
-
- content = ''
- for org, perms in self.org_perm_mapper.items():
- content += f'\n Orgnization: {org} \n Permissions: {perms} \n'
-
- message = _("""
-Hello %(name)s:
-\n
-The following asset permissions of organization %(org) will expire in three days
-\n
-%(content)s
- """) % {
- 'name': user.name,
- 'content': content,
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
-
-class AppPermWillExpireMsg(UserMessage):
+class PermedAppsWillExpireUserMsg(BasePermMsg):
def __init__(self, user, apps):
super().__init__(user)
self.apps = apps
def get_html_msg(self) -> dict:
- user = self.user
- subject = _('Applications may expire')
-
- apps_text = ','.join(str(app) for app in self.apps)
- message = _("""
- Hello %(name)s:
-
- Your permissions for the following applications may expire in three days:
-
- %(apps)s
-
- Please contact the administrator
- """) % {
- 'name': user.name,
- 'apps': apps_text
+ subject = _("Your permed applications is about to expire")
+ context = {
+ 'name': self.user.name,
+ 'item_type': _('permed applications'),
+ 'items': [str(app) for app in self.apps]
}
+ message = render_to_string('perms/_msg_permed_items_expire.html', context)
return {
'subject': subject,
'message': message
}
- def get_text_msg(self) -> dict:
- user = self.user
- subject = _('Applications may expire')
- apps_text = ','.join(str(app) for app in self.apps)
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ from applications.models import Application
- message = _("""
-Hello %(name)s:
-\n
-Your permissions for the following applications may expire in three days:
-\n
-%(apps)s
-\n
-Please contact the administrator
- """) % {
- 'name': user.name,
- 'apps': apps_text
- }
- return {
- 'subject': subject,
- 'message': message
- }
+ user = User.objects.first()
+ apps = Application.objects.all()[:10]
+ return cls(user, apps)
-class AppPermWillExpireForOrgAdminMsg(UserMessage):
+class AppPermsWillExpireForOrgAdminMsg(BasePermMsg):
def __init__(self, user, perms, org):
super().__init__(user)
self.perms = perms
self.org = org
- def get_html_msg(self) -> dict:
- user = self.user
- subject = _('Application permission will expired')
- perms_text = ','.join(str(perm) for perm in self.perms)
-
- message = _("""
- Hello %(name)s:
-
- The following application permissions of organization %(org) will expire in three days
-
- %(perms)s
- """) % {
- 'name': user.name,
- 'org': self.org,
- 'perms': perms_text
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
- def get_text_msg(self) -> dict:
- user = self.user
- subject = _('Application permission will expired')
- perms_text = ','.join(str(perm) for perm in self.perms)
-
- message = _("""
-Hello %(name)s:
-\n
-The following application permissions of organization %(org) will expire in three days
-\n
-%(perms)s
- """) % {
- 'name': user.name,
- 'org': self.org,
- 'perms': perms_text
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
-
-class AppPermWillExpireForAdminMsg(UserMessage):
- def __init__(self, user, org_perm_mapper: dict):
- super().__init__(user)
- self.org_perm_mapper = org_perm_mapper
+ def get_items_with_url(self):
+ items_with_url = []
+ for perm in self.perms:
+ url = js_reverse(
+ 'perms:application-permission-detail',
+ kwargs={'pk': perm.id}, external=True,
+ api_to_ui=True
+ ) + f'?oid={perm.org_id}'
+ items_with_url.append([perm.name, url])
+ return items_with_url
def get_html_msg(self) -> dict:
- user = self.user
- subject = _('Application permission will expired')
-
- content = ''
- for org, perms in self.org_perm_mapper.items():
- content += f'
Orgnization: {org}
Permissions: {",".join(str(perm) for perm in perms)}
'
-
- message = _("""
- Hello %(name)s:
-
- The following application permissions will expire in three days
-
- %(content)s
- """) % {
- 'name': user.name,
- 'content': content,
+ items = self.get_items_with_url()
+ subject = _('Application permissions is about to expire')
+ context = {
+ 'name': self.user.name,
+ 'item_type': _('application permissions of organization {}').format(self.org),
+ 'items_with_url': items
}
+ message = render_to_string('perms/_msg_item_permissions_expire.html', context)
return {
'subject': subject,
'message': message
}
- def get_text_msg(self) -> dict:
- user = self.user
- subject = _('Application permission will expired')
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ from perms.models import ApplicationPermission
+ from orgs.models import Organization
- content = ''
- for org, perms in self.org_perm_mapper.items():
- content += f'\n Orgnization: {org} \n Permissions: {perms} \n'
-
- message = _("""
-Hello %(name)s:
-\n
-The following application permissions of organization %(org) will expire in three days
-\n
-%(content)s
- """) % {
- 'name': user.name,
- 'content': content,
- }
- return {
- 'subject': subject,
- 'message': message
- }
+ user = User.objects.first()
+ perms = ApplicationPermission.objects.all()[:10]
+ org = Organization.objects.first()
+ return cls(user, perms, org)
diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py
index a5ab4992c..023913a41 100644
--- a/apps/perms/tasks.py
+++ b/apps/perms/tasks.py
@@ -7,14 +7,13 @@ from django.db.transaction import atomic
from django.conf import settings
from celery import shared_task
-from users.models import User
from orgs.utils import tmp_to_root_org
from common.utils import get_logger
-from common.utils.timezone import now, dt_formater, dt_parser
+from common.utils.timezone import local_now, dt_formatter, dt_parser
from ops.celery.decorator import register_as_period_task
from perms.notifications import (
- AssetPermWillExpireMsg, AssetPermWillExpireForOrgAdminMsg, AssetPermWillExpireForAdminMsg,
- AppPermWillExpireMsg, AppPermWillExpireForOrgAdminMsg, AppPermWillExpireForAdminMsg,
+ PermedWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg,
+ PermedAppsWillExpireUserMsg, AppPermsWillExpireForOrgAdminMsg
)
from perms.models import AssetPermission, ApplicationPermission
from perms.utils.asset.user_permission import UserGrantedTreeRefreshController
@@ -34,10 +33,10 @@ def check_asset_permission_expired():
setting_name = 'last_asset_perm_expired_check'
- end = now()
+ end = local_now()
default_start = end - timedelta(days=36000) # Long long ago in china
- defaults = {'value': dt_formater(default_start)}
+ defaults = {'value': dt_formatter(default_start)}
setting, created = Setting.objects.get_or_create(
name=setting_name, defaults=defaults
)
@@ -45,7 +44,7 @@ def check_asset_permission_expired():
start = default_start
else:
start = dt_parser(setting.value)
- setting.value = dt_formater(end)
+ setting.value = dt_formatter(end)
setting.save()
asset_perm_ids = AssetPermission.objects.filter(
@@ -61,14 +60,15 @@ def check_asset_permission_expired():
@atomic()
@tmp_to_root_org()
def check_asset_permission_will_expired():
- start = now()
+ start = local_now()
end = start + timedelta(days=3)
user_asset_mapper = defaultdict(set)
org_perm_mapper = defaultdict(set)
asset_perms = AssetPermission.objects.filter(
- date_expired__gte=start, date_expired__lte=end
+ date_expired__gte=start,
+ date_expired__lte=end
).distinct()
for asset_perm in asset_perms:
@@ -83,18 +83,12 @@ def check_asset_permission_will_expired():
user_asset_mapper[u].update(assets)
for user, assets in user_asset_mapper.items():
- AssetPermWillExpireMsg(user, assets).publish_async()
-
- admins = User.objects.filter(role=User.ROLE.ADMIN)
-
- if org_perm_mapper:
- for admin in admins:
- AssetPermWillExpireForAdminMsg(admin, org_perm_mapper).publish_async()
+ PermedWillExpireUserMsg(user, assets).publish_async()
for org, perms in org_perm_mapper.items():
- org_admins = org.admins.exclude(role=User.ROLE.ADMIN)
+ org_admins = org.admins.all()
for org_admin in org_admins:
- AssetPermWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()
+ AssetPermsWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()
@register_as_period_task(crontab='0 10 * * *')
@@ -102,11 +96,12 @@ def check_asset_permission_will_expired():
@atomic()
@tmp_to_root_org()
def check_app_permission_will_expired():
- start = now()
+ start = local_now()
end = start + timedelta(days=3)
app_perms = ApplicationPermission.objects.filter(
- date_expired__gte=start, date_expired__lte=end
+ date_expired__gte=start,
+ date_expired__lte=end
).distinct()
user_app_mapper = defaultdict(set)
@@ -121,15 +116,9 @@ def check_app_permission_will_expired():
user_app_mapper[u].update(apps)
for user, apps in user_app_mapper.items():
- AppPermWillExpireMsg(user, apps).publish_async()
-
- admins = User.objects.filter(role=User.ROLE.ADMIN)
-
- if org_perm_mapper:
- for admin in admins:
- AppPermWillExpireForAdminMsg(admin, org_perm_mapper).publish_async()
+ PermedAppsWillExpireUserMsg(user, apps).publish_async()
for org, perms in org_perm_mapper.items():
- org_admins = org.admins.exclude(role=User.ROLE.ADMIN)
+ org_admins = org.admins.all()
for org_admin in org_admins:
- AppPermWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()
+ AppPermsWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async()
diff --git a/apps/perms/templates/perms/_msg_item_permissions_expire.html b/apps/perms/templates/perms/_msg_item_permissions_expire.html
new file mode 100644
index 000000000..f96b5e61d
--- /dev/null
+++ b/apps/perms/templates/perms/_msg_item_permissions_expire.html
@@ -0,0 +1,16 @@
+{% load i18n %}
+
+ {% trans 'Hello' %} {{ name }},
+
+
+
+ {% blocktranslate %}
+ The following {{ item_type }} will expire in 3 days
+ {% endblocktranslate %}
+
+
+
+ {% for item, url in items_with_url %}
+ - {{ item }}
+ {% endfor %}
+
diff --git a/apps/perms/templates/perms/_msg_permed_items_expire.html b/apps/perms/templates/perms/_msg_permed_items_expire.html
new file mode 100644
index 000000000..6a73f1b5f
--- /dev/null
+++ b/apps/perms/templates/perms/_msg_permed_items_expire.html
@@ -0,0 +1,24 @@
+{% load i18n %}
+
+ {% trans 'Hello' %} {{ name }},
+
+
+
+ {% blocktranslate %}
+ The following {{ item_type }} will expire in 3 days
+ {% endblocktranslate %}
+
+
+
+ {% for item in items %}
+ - {{ item }}
+ {% endfor %}
+
+
+
+
+ ---
+
+ {% trans 'If you have any question, please contact the administrator' %}
+
+
diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py
index 1225de0a4..06e5038fb 100644
--- a/apps/settings/serializers/security.py
+++ b/apps/settings/serializers/security.py
@@ -111,8 +111,8 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri
help_text=_("Allow terminal register, after all terminal setup, you should disable this for security")
)
SECURITY_WATERMARK_ENABLED = serializers.BooleanField(
- required=True, label=_('Replay watermark'),
- help_text=_('Enabled, the session replay contains watermark information')
+ required=True, label=_('Enable watermark'),
+ help_text=_('Enabled, the web session and replay contains watermark information')
)
SECURITY_MAX_IDLE_TIME = serializers.IntegerField(
min_value=1, max_value=99999, required=False,
diff --git a/apps/terminal/notifications.py b/apps/terminal/notifications.py
index c263440c3..461bc94a1 100644
--- a/apps/terminal/notifications.py
+++ b/apps/terminal/notifications.py
@@ -1,7 +1,9 @@
from typing import Callable
+import textwrap
from django.utils.translation import gettext_lazy as _
from django.conf import settings
+from django.template.loader import render_to_string
from users.models import User
from common.utils import get_logger, reverse
@@ -68,60 +70,21 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage):
def __init__(self, command):
self.command = command
- def get_text_msg(self) -> dict:
- command = self.command
-
- with tmp_to_root_org():
- session = Session.objects.get(id=command['session'])
-
- session_detail_url = reverse(
- 'api-terminal:session-detail', kwargs={'pk': command['session']},
- external=True, api_to_ui=True
- )
-
- message = _("""
-Command: %(command)s
-Asset: %(hostname)s (%(host_ip)s)
-User: %(user)s
-Level: %(risk_level)s
-Session: %(session_detail_url)s?oid=%(oid)s
- """) % {
- 'command': command['input'],
- 'hostname': command['asset'],
- 'host_ip': session.asset_obj.ip,
- 'user': command['user'],
- 'risk_level': Command.get_risk_level_str(command['risk_level']),
- 'session_detail_url': session_detail_url,
- 'oid': session.org_id
- }
- return {
- 'subject': self.subject,
- 'message': message
- }
+ @classmethod
+ def gen_test_msg(cls):
+ command = Command.objects.first().to_dict()
+ return cls(command)
def get_html_msg(self) -> dict:
command = self.command
with tmp_to_root_org():
session = Session.objects.get(id=command['session'])
-
session_detail_url = reverse(
'api-terminal:session-detail', kwargs={'pk': command['session']},
external=True, api_to_ui=True
)
-
- message = _("""
- Command: %(command)s
-
- Asset: %(hostname)s (%(host_ip)s)
-
- User: %(user)s
-
- Level: %(risk_level)s
-
- Session: session detail
-
- """) % {
+ context = {
'command': command['input'],
'hostname': command['asset'],
'host_ip': session.asset_obj.ip,
@@ -130,6 +93,7 @@ Session: %(session_detail_url)s?oid=%(oid)s
'session_detail_url': session_detail_url,
'oid': session.org_id
}
+ message = render_to_string('terminal/_msg_command_alert.html', context)
return {
'subject': self.subject,
'message': message
@@ -144,53 +108,35 @@ class CommandExecutionAlert(CommandAlertMixin, SystemMessage):
def __init__(self, command):
self.command = command
+ @classmethod
+ def gen_test_msg(cls):
+ from assets.models import Asset
+ from users.models import User
+ cmd = {
+ 'input': 'ifconfig eth0',
+ 'assets': Asset.objects.all()[:10],
+ 'user': str(User.objects.first()),
+ 'risk_level': 5,
+ }
+ return cls(cmd)
+
def get_html_msg(self) -> dict:
command = self.command
_input = command['input']
_input = _input.replace('\n', '
')
- assets = ', '.join([str(asset) for asset in command['assets']])
- message = _("""
- Assets: %(assets)s
-
- User: %(user)s
-
- Level: %(risk_level)s
-
+ assets_with_url = []
+ for asset in command['assets']:
+ url = reverse('assets:asset-detail', kwargs={'pk': asset.id}, api_to_ui=True, external=True)
+ assets_with_url.append([asset, url])
- ----------------- Commands ----------------
- %(command)s
- ----------------- Commands ----------------
- """) % {
+ context = {
'command': _input,
- 'assets': assets,
- 'user': command['user'],
- 'risk_level': Command.get_risk_level_str(command['risk_level'])
- }
- return {
- 'subject': self.subject,
- 'message': message
- }
-
- def get_text_msg(self) -> dict:
- command = self.command
- _input = command['input']
-
- assets = ', '.join([str(asset) for asset in command['assets']])
- message = _("""
-Assets: %(assets)s
-User: %(user)s
-Level: %(risk_level)s
-
-Commands 👇 ------------
-%(command)s
-------------------------
- """) % {
- 'command': _input,
- 'assets': assets,
+ 'assets_with_url': assets_with_url,
'user': command['user'],
'risk_level': Command.get_risk_level_str(command['risk_level'])
}
+ message = render_to_string('terminal/_msg_command_execute_alert.html', context)
return {
'subject': self.subject,
'message': message
diff --git a/apps/terminal/templates/terminal/_msg_command_alert.html b/apps/terminal/templates/terminal/_msg_command_alert.html
new file mode 100644
index 000000000..b980e87bc
--- /dev/null
+++ b/apps/terminal/templates/terminal/_msg_command_alert.html
@@ -0,0 +1,17 @@
+{% load i18n %}
+
+
+ {% trans 'Command' %}: {{ command }}
+
+
+ {% trans 'Asset' %}: {{ hostname }}({{ host_ip }})
+
+
+ {% trans 'User' %}: {{ user }}
+
+
+ {% trans 'Level' %}: {{ risk_level }}
+
+
+ {% trans 'Session' %}: {% trans 'view' %}
+
diff --git a/apps/terminal/templates/terminal/_msg_command_execute_alert.html b/apps/terminal/templates/terminal/_msg_command_execute_alert.html
new file mode 100644
index 000000000..fcea490c6
--- /dev/null
+++ b/apps/terminal/templates/terminal/_msg_command_execute_alert.html
@@ -0,0 +1,24 @@
+{% load i18n %}
+
+
+ {% trans 'User' %}: {{ user }}
+
+
+ {% trans 'Level' %}: {{ risk_level }}
+
+
+
{% trans 'Command' %}:
+
+ {{ command }}
+
+
+
+
{% trans 'Assets' %}:
+
+ {% for asset, url in assets_with_url %}
+ -
+ {{ asset }}
+
+ {% endfor %}
+
+
diff --git a/apps/tickets/apps.py b/apps/tickets/apps.py
index d155cbde2..a7beac9a4 100644
--- a/apps/tickets/apps.py
+++ b/apps/tickets/apps.py
@@ -6,4 +6,5 @@ class TicketsConfig(AppConfig):
def ready(self):
from . import signals_handler
+ from . import notifications
return super().ready()
diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py
index e0fba3d6c..22e3477c6 100644
--- a/apps/tickets/notifications.py
+++ b/apps/tickets/notifications.py
@@ -1,70 +1,49 @@
from urllib.parse import urljoin
from django.conf import settings
+from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from . import const
from notifications.notifications import UserMessage
from common.utils import get_logger
+from .models import Ticket
logger = get_logger(__file__)
-EMAIL_TEMPLATE = '''
-
-
- {title}
-
-
- {body}
-
-
-
-'''
-
class BaseTicketMessage(UserMessage):
+ title: ''
+ ticket: Ticket
+ content_title: str
@property
def subject(self):
- return _(self.title).format(self.ticket.title, self.ticket.get_type_display())
+ return self.title.format(self.ticket.title, self.ticket.get_type_display())
@property
def ticket_detail_url(self):
return urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(self.ticket.id)))
- def get_text_msg(self) -> dict:
- message = """
- {title}: {ticket_detail_url}
- {body}
- """.format(
+ def get_html_msg(self) -> dict:
+ context = dict(
title=self.content_title,
ticket_detail_url=self.ticket_detail_url,
- body=self.ticket.body.replace('', '').replace('
', '')
+ body=self.ticket.body.replace('\n', '
'),
)
+ message = render_to_string('tickets/_msg_ticket.html', context)
return {
'subject': self.subject,
'message': message
}
- def get_html_msg(self) -> dict:
- message = EMAIL_TEMPLATE.format(
- title=self.content_title,
- ticket_detail_url=self.ticket_detail_url,
- ticket_detail_url_description=_('click here to review'),
- body=self.ticket.body.replace('\n', '
'),
- )
- return {
- 'subject': self.subject,
- 'message': message
- }
+ @classmethod
+ def gen_test_msg(cls):
+ return cls(None)
class TicketAppliedToAssignee(BaseTicketMessage):
- title = 'New Ticket - {} ({})'
+ title = _('New Ticket - {} ({})')
def __init__(self, user, ticket):
self.ticket = ticket
@@ -72,11 +51,21 @@ class TicketAppliedToAssignee(BaseTicketMessage):
@property
def content_title(self):
- return _('Your has a new ticket, applicant - {}').format(str(self.ticket.applicant_display))
+ return _('Your has a new ticket, applicant - {}').format(
+ str(self.ticket.applicant_display)
+ )
+
+ @classmethod
+ def gen_test_msg(cls):
+ from .models import Ticket
+ from users.models import User
+ ticket = Ticket.objects.first()
+ user = User.objects.first()
+ return cls(user, ticket)
class TicketProcessedToApplicant(BaseTicketMessage):
- title = 'Ticket has processed - {} ({})'
+ title = _('Ticket has processed - {} ({})')
def __init__(self, user, ticket, processor):
self.ticket = ticket
@@ -86,3 +75,12 @@ class TicketProcessedToApplicant(BaseTicketMessage):
@property
def content_title(self):
return _('Your ticket has been processed, processor - {}').format(str(self.processor))
+
+ @classmethod
+ def gen_test_msg(cls):
+ from .models import Ticket
+ from users.models import User
+ ticket = Ticket.objects.first()
+ user = User.objects.first()
+ processor = User.objects.last()
+ return cls(user, ticket, processor)
diff --git a/apps/tickets/templates/tickets/_msg_ticket.html b/apps/tickets/templates/tickets/_msg_ticket.html
new file mode 100644
index 000000000..8fc5afb81
--- /dev/null
+++ b/apps/tickets/templates/tickets/_msg_ticket.html
@@ -0,0 +1,14 @@
+{% load i18n %}
+
+
+ {{ title}}
+
+
+ {{ body}}
+
+
+
\ No newline at end of file
diff --git a/apps/users/apps.py b/apps/users/apps.py
index f37fa6cf5..a40d54d84 100644
--- a/apps/users/apps.py
+++ b/apps/users/apps.py
@@ -8,4 +8,5 @@ class UsersConfig(AppConfig):
def ready(self):
from . import signals_handler
+ from . import notifications
super().ready()
diff --git a/apps/users/notifications.py b/apps/users/notifications.py
index 57b2c9308..ab04b6fae 100644
--- a/apps/users/notifications.py
+++ b/apps/users/notifications.py
@@ -1,84 +1,42 @@
from datetime import datetime
from urllib.parse import urljoin
+from django.utils import timezone
from django.utils.translation import ugettext as _
from django.conf import settings
+from django.template.loader import render_to_string
-from common.utils import reverse, get_request_ip_or_data, get_request_user_agent, lazyproperty
+from common.utils import reverse, get_request_ip_or_data, get_request_user_agent
from notifications.notifications import UserMessage
class ResetPasswordMsg(UserMessage):
-
def __init__(self, user):
super().__init__(user)
self.reset_passwd_token = user.generate_reset_token()
- def get_text_msg(self) -> dict:
- user = self.user
- subject = _('Reset password')
- message = _("""
-Hello %(name)s:
-Please click the link below to reset your password, if not your request, concern your account security
-
-Click here reset password 👇
-%(rest_password_url)s?token=%(rest_password_token)s
-
-This link is valid for 1 hour. After it expires,
-
-request new one 👇
-%(forget_password_url)s?email=%(email)s
-
--------------------
-
-Login direct 👇
-%(login_url)s
-
-""") % {
- 'name': user.name,
- 'rest_password_url': reverse('authentication:reset-password', external=True),
- 'rest_password_token': self.reset_passwd_token,
- 'forget_password_url': reverse('authentication:forgot-password', external=True),
- 'email': user.email,
- 'login_url': reverse('authentication:login', external=True),
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
def get_html_msg(self) -> dict:
user = self.user
subject = _('Reset password')
- message = _("""
- Hello %(name)s:
-
- Please click the link below to reset your password, if not your request, concern your account security
-
- Click here reset password
-
- This link is valid for 1 hour. After it expires, request new one
-
-
- ---
-
-
- Login direct
-
-
- """) % {
- 'name': user.name,
+ context = {
+ 'user': user,
'rest_password_url': reverse('authentication:reset-password', external=True),
'rest_password_token': self.reset_passwd_token,
'forget_password_url': reverse('authentication:forgot-password', external=True),
- 'email': user.email,
'login_url': reverse('authentication:login', external=True),
}
+ message = render_to_string('authentication/_msg_reset_password.html', context)
return {
'subject': subject,
'message': message
}
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ user = User.objects.first()
+ return cls(user)
+
class ResetPasswordSuccessMsg(UserMessage):
def __init__(self, user, request):
@@ -86,280 +44,119 @@ class ResetPasswordSuccessMsg(UserMessage):
self.ip_address = get_request_ip_or_data(request)
self.browser = get_request_user_agent(request)
- def get_text_msg(self) -> dict:
- user = self.user
-
- subject = _('Reset password success')
- message = _("""
-
-Hi %(name)s:
-
-Your JumpServer password has just been successfully updated.
-
-If the password update was not initiated by you, your account may have security issues.
-It is recommended that you log on to the JumpServer immediately and change your password.
-
-If you have any questions, you can contact the administrator.
-
--------------------
-
-
-IP Address: %(ip_address)s
-\n
-Browser: %(browser)s
-
- """) % {
- 'name': user.name,
- 'ip_address': self.ip_address,
- 'browser': self.browser,
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
def get_html_msg(self) -> dict:
user = self.user
subject = _('Reset password success')
- message = _("""
-
- Hi %(name)s:
-
-
-
-
- Your JumpServer password has just been successfully updated.
-
-
-
- If the password update was not initiated by you, your account may have security issues.
- It is recommended that you log on to the JumpServer immediately and change your password.
-
-
-
- If you have any questions, you can contact the administrator.
-
-
- ---
-
-
- IP Address: %(ip_address)s
-
-
- Browser: %(browser)s
-
-
- """) % {
+ context = {
'name': user.name,
'ip_address': self.ip_address,
'browser': self.browser,
}
+ message = render_to_string('authentication/_msg_rest_password_success.html', context)
return {
'subject': subject,
'message': message
}
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ from rest_framework.test import APIRequestFactory
+ from rest_framework.request import Request
+ factory = APIRequestFactory()
+ request = Request(factory.get('/notes/'))
+ user = User.objects.first()
+ return cls(user, request)
+
class PasswordExpirationReminderMsg(UserMessage):
update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate')
- def get_text_msg(self) -> dict:
- user = self.user
-
- subject = _('Security notice')
- message = _("""
-Hello %(name)s:
-
-Your password will expire in %(date_password_expired)s,
-
-For your account security, please click on the link below to update your password in time
-
-Click here update password 👇
-%(update_password_url)s
-
-If your password has expired, please click 👇 to apply for a password reset email.
-%(forget_password_url)s?email=%(email)s
-
--------------------
-
-Login direct 👇
-%(login_url)s
-
- """) % {
- 'name': user.name,
- 'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
- user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
- 'update_password_url': self.update_password_url,
- 'forget_password_url': reverse('authentication:forgot-password', external=True),
- 'email': user.email,
- 'login_url': reverse('authentication:login', external=True),
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
def get_html_msg(self) -> dict:
user = self.user
+ subject = _('Password is about expire')
- subject = _('Security notice')
- message = _("""
- Hello %(name)s:
-
- Your password will expire in %(date_password_expired)s,
-
- For your account security, please click on the link below to update your password in time
-
- Click here update password
-
- If your password has expired, please click
- Password expired
- to apply for a password reset email.
-
-
- ---
-
-
- Login direct
-
-
- """) % {
+ date_password_expired_local = timezone.localtime(user.date_password_expired)
+ date_password_expired = date_password_expired_local.strftime('%Y-%m-%d %H:%M:%S')
+ context = {
'name': user.name,
- 'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
- user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
+ 'date_password_expired': date_password_expired,
'update_password_url': self.update_password_url,
'forget_password_url': reverse('authentication:forgot-password', external=True),
'email': user.email,
'login_url': reverse('authentication:login', external=True),
}
+ message = render_to_string('users/_msg_password_expire_reminder.html', context)
return {
'subject': subject,
'message': message
}
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ user = User.objects.get(username='admin')
+ return cls(user)
+
class UserExpirationReminderMsg(UserMessage):
- def get_text_msg(self) -> dict:
- subject = _('Expiration notice')
- message = _("""
-Hello %(name)s:
-
-Your account will expire in %(date_expired)s,
-
-In order not to affect your normal work, please contact the administrator for confirmation.
-
- """) % {
- 'name': self.user.name,
- 'date_expired': datetime.fromtimestamp(datetime.timestamp(
- self.user.date_expired)).strftime('%Y-%m-%d %H:%M'),
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
def get_html_msg(self) -> dict:
- subject = _('Expiration notice')
- message = _("""
- Hello %(name)s:
-
- Your account will expire in %(date_expired)s,
-
- In order not to affect your normal work, please contact the administrator for confirmation.
-
- """) % {
- 'name': self.user.name,
- 'date_expired': datetime.fromtimestamp(datetime.timestamp(
- self.user.date_expired)).strftime('%Y-%m-%d %H:%M'),
+ subject = _('Account is about expire')
+ date_expired_local = timezone.localtime(self.user.date_password_expired)
+ date_expired = date_expired_local.strftime('%Y-%m-%d %H:%M:%S')
+ context = {
+ 'name': self.user.name,
+ 'date_expired': date_expired
}
+ message = render_to_string('users/_msg_account_expire_reminder.html', context)
return {
'subject': subject,
'message': message
}
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ user = User.objects.get(username='admin')
+ return cls(user)
+
class ResetSSHKeyMsg(UserMessage):
- def get_text_msg(self) -> dict:
- subject = _('SSH Key Reset')
- message = _("""
-Hello %(name)s:
-
-Your ssh public key has been reset by site administrator.
-Please login and reset your ssh public key.
-
-Login direct 👇
-%(login_url)s
-
- """) % {
- 'name': self.user.name,
- 'login_url': reverse('authentication:login', external=True),
- }
-
- return {
- 'subject': subject,
- 'message': message
- }
-
def get_html_msg(self) -> dict:
- subject = _('SSH Key Reset')
- message = _("""
- Hello %(name)s:
-
- Your ssh public key has been reset by site administrator.
- Please login and reset your ssh public key.
-
- Login direct
-
-
- """) % {
+ subject = _('Reset SSH Key')
+ context = {
'name': self.user.name,
'login_url': reverse('authentication:login', external=True),
}
-
+ message = render_to_string('users/_msg_reset_ssh_key.html', context)
return {
'subject': subject,
'message': message
}
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ user = User.objects.get(username='admin')
+ return cls(user)
+
class ResetMFAMsg(UserMessage):
- def get_text_msg(self) -> dict:
- subject = _('MFA Reset')
- message = _("""
-Hello %(name)s:
-
-Your MFA has been reset by site administrator.
-Please login and reset your MFA.
-
-Login direct 👇
-%(login_url)s
-
- """) % {
- 'name': self.user.name,
- 'login_url': reverse('authentication:login', external=True),
- }
- return {
- 'subject': subject,
- 'message': message
- }
-
def get_html_msg(self) -> dict:
- subject = _('MFA Reset')
- message = _("""
- Hello %(name)s:
-
- Your MFA has been reset by site administrator.
- Please login and reset your MFA.
-
- Login direct
-
-
- """) % {
+ subject = _('Reset MFA')
+ context = {
'name': self.user.name,
'login_url': reverse('authentication:login', external=True),
}
+ message = render_to_string('users/_msg_reset_mfa.html', context)
return {
'subject': subject,
'message': message
}
+
+ @classmethod
+ def gen_test_msg(cls):
+ from users.models import User
+ user = User.objects.get(username='admin')
+ return cls(user)
diff --git a/apps/users/templates/users/_msg_account_expire_reminder.html b/apps/users/templates/users/_msg_account_expire_reminder.html
new file mode 100644
index 000000000..c6799624f
--- /dev/null
+++ b/apps/users/templates/users/_msg_account_expire_reminder.html
@@ -0,0 +1,9 @@
+{% load i18n %}
+
+
+ {% trans 'Hello' %} {{ name }},
+
+
+ {% trans 'Your account will expire in' %} {{ date_expired }},
+ {% trans 'In order not to affect your normal work, please contact the administrator for confirmation.' %}
+
diff --git a/apps/users/templates/users/_msg_password_expire_reminder.html b/apps/users/templates/users/_msg_password_expire_reminder.html
new file mode 100644
index 000000000..5037a332f
--- /dev/null
+++ b/apps/users/templates/users/_msg_password_expire_reminder.html
@@ -0,0 +1,22 @@
+{% load i18n %}
+
+
+ {% trans 'Hello' %} {{ name }},
+
+
+ {% trans 'Your password will expire in' %} {{ date_password_expired }},
+ {% trans 'For your account security, please click on the link below to update your password in time' %}
+
+
+ {% trans 'Click here update password' %}
+
+
+
+
+ {% trans 'If your password has expired, please click' %}
+ {% trans 'Reset password' %}
+ {% trans 'to apply for a password reset email.' %}
+
+---
+
+{% trans 'Login direct' %}
\ No newline at end of file
diff --git a/apps/users/templates/users/_msg_reset_mfa.html b/apps/users/templates/users/_msg_reset_mfa.html
new file mode 100644
index 000000000..073d97ccb
--- /dev/null
+++ b/apps/users/templates/users/_msg_reset_mfa.html
@@ -0,0 +1,12 @@
+{% load i18n %}
+
+
+ {% trans 'Hello' %} {{ name }},
+
+
+ {% trans 'Your MFA has been reset by site administrator.' %}
+ {% trans 'Please login and reset your MFA.' %}
+
+
+ {% trans 'Login direct' %}
+
\ No newline at end of file
diff --git a/apps/users/templates/users/_msg_reset_ssh_key.html b/apps/users/templates/users/_msg_reset_ssh_key.html
new file mode 100644
index 000000000..ab705aca7
--- /dev/null
+++ b/apps/users/templates/users/_msg_reset_ssh_key.html
@@ -0,0 +1,12 @@
+{% load i18n %}
+
+
+ {% trans 'Hello' %} {{ name }},
+
+
+ {% trans 'Your ssh public key has been reset by site administrator.' %}
+ {% trans 'Please login and reset your ssh public key.' %}
+
+
+ {% trans 'Login direct' %}
+
\ No newline at end of file
diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py
index 7966dda8e..545ebf36d 100644
--- a/apps/users/views/profile/otp.py
+++ b/apps/users/views/profile/otp.py
@@ -190,7 +190,7 @@ class UserOtpSettingsSuccessView(TemplateView):
title, describe = self.get_title_describe()
context = {
'title': title,
- 'messages': describe,
+ 'message': describe,
'interval': 1,
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index a33745ae1..49d7c5a5e 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -119,3 +119,4 @@ cx-Oracle==8.2.1
psycopg2-binary==2.9.1
alibabacloud_dysmsapi20170525==2.0.2
geoip2==4.4.0
+html2text==2020.1.16
\ No newline at end of file