perf: 支持在线会话暂停操作 (#11146)

* perf: 支持在线会话暂停操作

* perf: 优化代码

---------

Co-authored-by: Eric <xplzv@126.com>
This commit is contained in:
fit2bot 2023-08-01 16:40:38 +08:00 committed by GitHub
parent d17e2cde06
commit 44397caad4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 231 additions and 99 deletions

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-28 10:57+0800\n" "POT-Creation-Date: 2023-07-31 16:39+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
#: accounts/api/automations/base.py:79 #: accounts/api/automations/base.py:79 tickets/api/ticket.py:112
msgid "The parameter 'action' must be [{}]" msgid "The parameter 'action' must be [{}]"
msgstr "パラメータ 'action' は [{}] でなければなりません。" msgstr "パラメータ 'action' は [{}] でなければなりません。"
@ -502,7 +502,7 @@ msgstr "アカウントの確認"
#: settings/models.py:32 settings/serializers/sms.py:6 #: settings/models.py:32 settings/serializers/sms.py:6
#: terminal/models/applet/applet.py:32 terminal/models/component/endpoint.py:12 #: terminal/models/applet/applet.py:32 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:92 #: terminal/models/component/endpoint.py:92
#: terminal/models/component/storage.py:26 terminal/models/component/task.py:15 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:13
#: terminal/models/component/terminal.py:84 users/forms/profile.py:33 #: terminal/models/component/terminal.py:84 users/forms/profile.py:33
#: users/models/group.py:13 users/models/user.py:753 #: users/models/group.py:13 users/models/user.py:753
#: xpack/plugins/cloud/models.py:28 #: xpack/plugins/cloud/models.py:28
@ -1245,7 +1245,7 @@ msgstr "SSHパブリックキー"
#: assets/models/cmd_filter.py:88 assets/models/group.py:20 #: assets/models/cmd_filter.py:88 assets/models/group.py:20
#: common/db/models.py:36 ops/models/adhoc.py:26 ops/models/job.py:113 #: common/db/models.py:36 ops/models/adhoc.py:26 ops/models/job.py:113
#: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:37 #: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:37
#: terminal/models/applet/applet.py:44 terminal/models/applet/applet.py:248 #: terminal/models/applet/applet.py:44 terminal/models/applet/applet.py:250
#: terminal/models/applet/host.py:141 terminal/models/component/endpoint.py:24 #: terminal/models/applet/host.py:141 terminal/models/component/endpoint.py:24
#: terminal/models/component/endpoint.py:102 #: terminal/models/component/endpoint.py:102
#: terminal/models/session/session.py:46 tickets/models/comment.py:32 #: terminal/models/session/session.py:46 tickets/models/comment.py:32
@ -1439,7 +1439,7 @@ msgstr "アセットの自動化タスク"
#: assets/models/automations/base.py:113 audits/models.py:199 #: assets/models/automations/base.py:113 audits/models.py:199
#: audits/serializers.py:50 ops/models/base.py:49 ops/models/job.py:186 #: audits/serializers.py:50 ops/models/base.py:49 ops/models/job.py:186
#: terminal/models/applet/applet.py:247 terminal/models/applet/host.py:138 #: terminal/models/applet/applet.py:249 terminal/models/applet/host.py:138
#: terminal/models/component/status.py:30 terminal/serializers/applet.py:18 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18
#: terminal/serializers/applet_host.py:115 tickets/models/ticket/general.py:283 #: terminal/serializers/applet_host.py:115 tickets/models/ticket/general.py:283
#: tickets/serializers/super_ticket.py:13 #: tickets/serializers/super_ticket.py:13
@ -2048,7 +2048,7 @@ msgid "Login log"
msgstr "ログインログ" msgstr "ログインログ"
#: audits/const.py:43 terminal/models/applet/host.py:142 #: audits/const.py:43 terminal/models/applet/host.py:142
#: terminal/models/component/task.py:24 #: terminal/models/component/task.py:22
msgid "Task" msgid "Task"
msgstr "タスク" msgstr "タスク"
@ -2248,29 +2248,29 @@ msgstr "外部ストレージへのFTPファイルのアップロード"
msgid "This action require verify your MFA" msgid "This action require verify your MFA"
msgstr "この操作には、MFAを検証する必要があります" msgstr "この操作には、MFAを検証する必要があります"
#: authentication/api/connection_token.py:221 #: authentication/api/connection_token.py:230
msgid "Reusable connection token is not allowed, global setting not enabled" msgid "Reusable connection token is not allowed, global setting not enabled"
msgstr "" msgstr ""
"再使用可能な接続トークンの使用は許可されていません。グローバル設定は有効に" "再使用可能な接続トークンの使用は許可されていません。グローバル設定は有効に"
"なっていません" "なっていません"
#: authentication/api/connection_token.py:301 #: authentication/api/connection_token.py:310
msgid "Anonymous account is not supported for this asset" msgid "Anonymous account is not supported for this asset"
msgstr "匿名アカウントはこのプロパティではサポートされていません" msgstr "匿名アカウントはこのプロパティではサポートされていません"
#: authentication/api/connection_token.py:320 #: authentication/api/connection_token.py:329
msgid "Account not found" msgid "Account not found"
msgstr "アカウントが見つかりません" msgstr "アカウントが見つかりません"
#: authentication/api/connection_token.py:323 #: authentication/api/connection_token.py:332
msgid "Permission expired" msgid "Permission expired"
msgstr "承認の有効期限が切れています" msgstr "承認の有効期限が切れています"
#: authentication/api/connection_token.py:337 #: authentication/api/connection_token.py:346
msgid "ACL action is reject: {}({})" msgid "ACL action is reject: {}({})"
msgstr "ACL アクションは拒否です: {}({})" msgstr "ACL アクションは拒否です: {}({})"
#: authentication/api/connection_token.py:341 #: authentication/api/connection_token.py:350
msgid "ACL action is review" msgid "ACL action is review"
msgstr "ACL アクションはレビューです" msgstr "ACL アクションはレビューです"
@ -3691,7 +3691,7 @@ msgid "Module"
msgstr "モジュール" msgstr "モジュール"
#: ops/models/adhoc.py:24 ops/models/celery.py:58 ops/models/job.py:97 #: ops/models/adhoc.py:24 ops/models/celery.py:58 ops/models/job.py:97
#: terminal/models/component/task.py:16 #: terminal/models/component/task.py:14
msgid "Args" msgid "Args"
msgstr "アルグ" msgstr "アルグ"
@ -3734,7 +3734,7 @@ msgstr "Celery タスク#タスク#"
msgid "Can view task monitor" msgid "Can view task monitor"
msgstr "タスクモニターを表示できます" msgstr "タスクモニターを表示できます"
#: ops/models/celery.py:59 terminal/models/component/task.py:17 #: ops/models/celery.py:59 terminal/models/component/task.py:15
msgid "Kwargs" msgid "Kwargs"
msgstr "クワーグ" msgstr "クワーグ"
@ -3871,7 +3871,7 @@ msgstr "例外ジョブのクリーンアップ"
msgid "Task log" msgid "Task log"
msgstr "タスクログ" msgstr "タスクログ"
#: ops/templates/ops/celery_task_log.html:71 #: ops/templates/ops/celery_task_log.html:71 terminal/serializers/task.py:10
msgid "Task name" msgid "Task name"
msgstr "タスク名" msgstr "タスク名"
@ -4253,7 +4253,7 @@ msgid "My assets"
msgstr "私の資産" msgstr "私の資産"
#: rbac/tree.py:56 terminal/models/applet/applet.py:51 #: rbac/tree.py:56 terminal/models/applet/applet.py:51
#: terminal/models/applet/applet.py:244 terminal/models/applet/host.py:29 #: terminal/models/applet/applet.py:246 terminal/models/applet/host.py:29
#: terminal/serializers/applet.py:15 #: terminal/serializers/applet.py:15
msgid "Applet" msgid "Applet"
msgstr "リモートアプリケーション" msgstr "リモートアプリケーション"
@ -5563,8 +5563,8 @@ msgstr "期限切れです。"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your password has expired, please click <a " " Your password has expired, please click <a href="
"href=\"%(user_password_update_url)s\"> this link </a> update password.\n" "\"%(user_password_update_url)s\"> this link </a> update password.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
@ -5585,34 +5585,34 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
" クリックしてください <a " " クリックしてください <a href=\"%(user_password_update_url)s"
"href=\"%(user_password_update_url)s\"> リンク </a> パスワードの更新\n" "\"> リンク </a> パスワードの更新\n"
" " " "
#: templates/_message.html:43 #: templates/_message.html:43
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your information was incomplete. Please click <a " " Your information was incomplete. Please click <a href="
"href=\"%(first_login_url)s\"> this link </a>to complete your information.\n" "\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" あなたの情報が不完全なので、クリックしてください。 <a " " あなたの情報が不完全なので、クリックしてください。 <a href="
"href=\"%(first_login_url)s\"> リンク </a> 補完\n" "\"%(first_login_url)s\"> リンク </a> 補完\n"
" " " "
#: templates/_message.html:56 #: templates/_message.html:56
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your ssh public key not set or expired. Please click <a " " Your ssh public key not set or expired. Please click <a href="
"href=\"%(user_pubkey_update)s\"> this link </a>to update\n" "\"%(user_pubkey_update)s\"> this link </a>to update\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" SSHキーが設定されていないか無効になっている場合は、 <a " " SSHキーが設定されていないか無効になっている場合は、 <a href="
"href=\"%(user_pubkey_update)s\"> リンク </a> 更新\n" "\"%(user_pubkey_update)s\"> リンク </a> 更新\n"
" " " "
#: templates/_mfa_login_field.html:28 #: templates/_mfa_login_field.html:28
@ -5805,6 +5805,18 @@ msgstr "読み取り専用"
msgid "Writable" msgid "Writable"
msgstr "書き込み可能" msgstr "書き込み可能"
#: terminal/const.py:94
msgid "Kill Session"
msgstr "セッションを終了する"
#: terminal/const.py:95
msgid "Lock Session"
msgstr "セッションをロックする"
#: terminal/const.py:96
msgid "Unlock Session"
msgstr "セッションのロックを解除する"
#: terminal/exceptions.py:8 #: terminal/exceptions.py:8
msgid "Bulk create not support" msgid "Bulk create not support"
msgstr "一括作成非サポート" msgstr "一括作成非サポート"
@ -5857,7 +5869,7 @@ msgstr "カスタムプラットフォームのみをサポート"
msgid "Missing type in platform.yml" msgid "Missing type in platform.yml"
msgstr "platform.ymlにタイプがありません" msgstr "platform.ymlにタイプがありません"
#: terminal/models/applet/applet.py:246 terminal/models/applet/host.py:35 #: terminal/models/applet/applet.py:248 terminal/models/applet/host.py:35
#: terminal/models/applet/host.py:136 #: terminal/models/applet/host.py:136
msgid "Hosting" msgid "Hosting"
msgstr "ホスト マシン" msgstr "ホスト マシン"
@ -6028,23 +6040,23 @@ msgstr "リプレイ"
msgid "Date end" msgid "Date end"
msgstr "終了日" msgstr "終了日"
#: terminal/models/session/session.py:240 #: terminal/models/session/session.py:261
msgid "Session record" msgid "Session record"
msgstr "セッション記録" msgstr "セッション記録"
#: terminal/models/session/session.py:242 #: terminal/models/session/session.py:263
msgid "Can monitor session" msgid "Can monitor session"
msgstr "セッションを監視できます" msgstr "セッションを監視できます"
#: terminal/models/session/session.py:243 #: terminal/models/session/session.py:264
msgid "Can share session" msgid "Can share session"
msgstr "セッションを共有できます" msgstr "セッションを共有できます"
#: terminal/models/session/session.py:244 #: terminal/models/session/session.py:265
msgid "Can terminate session" msgid "Can terminate session"
msgstr "セッションを終了できます" msgstr "セッションを終了できます"
#: terminal/models/session/session.py:245 #: terminal/models/session/session.py:266
msgid "Can validate session action perm" msgid "Can validate session action perm"
msgstr "セッションアクションのパーマを検証できます" msgstr "セッションアクションのパーマを検証できます"
@ -6213,7 +6225,12 @@ msgid ""
"has a private account, the other is public, when the application does not " "has a private account, the other is public, when the application does not "
"support multiple open and the special has been used, the public account will " "support multiple open and the special has been used, the public account will "
"be used to connect" "be used to connect"
msgstr "これらのアカウントは、公開されたアプリケーションに接続するために使用されます。アカウントは現在、2つのタイプに分類されています。1つは、各アカウントに専用のアカウントで、各ユーザーにはプライベートアカウントがあります。もう1つは公開されています。アプリケーションが複数のオープンをサポートしていない場合、および特別なものが使用されている場合、公開アカウントが使用されます。" msgstr ""
"これらのアカウントは、公開されたアプリケーションに接続するために使用されま"
"す。アカウントは現在、2つのタイプに分類されています。1つは、各アカウントに専"
"用のアカウントで、各ユーザーにはプライベートアカウントがあります。もう1つは公"
"開されています。アプリケーションが複数のオープンをサポートしていない場合、お"
"よび特別なものが使用されている場合、公開アカウントが使用されます。"
#: terminal/serializers/applet_host.py:77 #: terminal/serializers/applet_host.py:77
msgid "The number of public accounts created automatically" msgid "The number of public accounts created automatically"
@ -6388,6 +6405,10 @@ msgstr "インデックス"
msgid "Doc type" msgid "Doc type"
msgstr "Docタイプ" msgstr "Docタイプ"
#: terminal/serializers/task.py:9
msgid "Session id"
msgstr "セッション"
#: terminal/serializers/terminal.py:83 terminal/serializers/terminal.py:91 #: terminal/serializers/terminal.py:83 terminal/serializers/terminal.py:91
msgid "Not found" msgid "Not found"
msgstr "見つかりません" msgstr "見つかりません"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-28 10:57+0800\n" "POT-Creation-Date: 2023-07-31 16:39+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -17,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.3\n" "X-Generator: Poedit 2.4.3\n"
#: accounts/api/automations/base.py:79 #: accounts/api/automations/base.py:79 tickets/api/ticket.py:112
msgid "The parameter 'action' must be [{}]" msgid "The parameter 'action' must be [{}]"
msgstr "参数 'action' 必须是 [{}]" msgstr "参数 'action' 必须是 [{}]"
@ -501,7 +501,7 @@ msgstr "账号验证"
#: settings/models.py:32 settings/serializers/sms.py:6 #: settings/models.py:32 settings/serializers/sms.py:6
#: terminal/models/applet/applet.py:32 terminal/models/component/endpoint.py:12 #: terminal/models/applet/applet.py:32 terminal/models/component/endpoint.py:12
#: terminal/models/component/endpoint.py:92 #: terminal/models/component/endpoint.py:92
#: terminal/models/component/storage.py:26 terminal/models/component/task.py:15 #: terminal/models/component/storage.py:26 terminal/models/component/task.py:13
#: terminal/models/component/terminal.py:84 users/forms/profile.py:33 #: terminal/models/component/terminal.py:84 users/forms/profile.py:33
#: users/models/group.py:13 users/models/user.py:753 #: users/models/group.py:13 users/models/user.py:753
#: xpack/plugins/cloud/models.py:28 #: xpack/plugins/cloud/models.py:28
@ -1238,7 +1238,7 @@ msgstr "SSH公钥"
#: assets/models/cmd_filter.py:88 assets/models/group.py:20 #: assets/models/cmd_filter.py:88 assets/models/group.py:20
#: common/db/models.py:36 ops/models/adhoc.py:26 ops/models/job.py:113 #: common/db/models.py:36 ops/models/adhoc.py:26 ops/models/job.py:113
#: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:37 #: ops/models/playbook.py:26 rbac/models/role.py:37 settings/models.py:37
#: terminal/models/applet/applet.py:44 terminal/models/applet/applet.py:248 #: terminal/models/applet/applet.py:44 terminal/models/applet/applet.py:250
#: terminal/models/applet/host.py:141 terminal/models/component/endpoint.py:24 #: terminal/models/applet/host.py:141 terminal/models/component/endpoint.py:24
#: terminal/models/component/endpoint.py:102 #: terminal/models/component/endpoint.py:102
#: terminal/models/session/session.py:46 tickets/models/comment.py:32 #: terminal/models/session/session.py:46 tickets/models/comment.py:32
@ -1432,7 +1432,7 @@ msgstr "资产自动化任务"
#: assets/models/automations/base.py:113 audits/models.py:199 #: assets/models/automations/base.py:113 audits/models.py:199
#: audits/serializers.py:50 ops/models/base.py:49 ops/models/job.py:186 #: audits/serializers.py:50 ops/models/base.py:49 ops/models/job.py:186
#: terminal/models/applet/applet.py:247 terminal/models/applet/host.py:138 #: terminal/models/applet/applet.py:249 terminal/models/applet/host.py:138
#: terminal/models/component/status.py:30 terminal/serializers/applet.py:18 #: terminal/models/component/status.py:30 terminal/serializers/applet.py:18
#: terminal/serializers/applet_host.py:115 tickets/models/ticket/general.py:283 #: terminal/serializers/applet_host.py:115 tickets/models/ticket/general.py:283
#: tickets/serializers/super_ticket.py:13 #: tickets/serializers/super_ticket.py:13
@ -2032,7 +2032,7 @@ msgid "Login log"
msgstr "登录日志" msgstr "登录日志"
#: audits/const.py:43 terminal/models/applet/host.py:142 #: audits/const.py:43 terminal/models/applet/host.py:142
#: terminal/models/component/task.py:24 #: terminal/models/component/task.py:22
msgid "Task" msgid "Task"
msgstr "任务" msgstr "任务"
@ -2232,27 +2232,27 @@ msgstr "上传 FTP 文件到外部存储"
msgid "This action require verify your MFA" msgid "This action require verify your MFA"
msgstr "该操作需要验证您的 MFA, 请先开启并配置" msgstr "该操作需要验证您的 MFA, 请先开启并配置"
#: authentication/api/connection_token.py:221 #: authentication/api/connection_token.py:230
msgid "Reusable connection token is not allowed, global setting not enabled" msgid "Reusable connection token is not allowed, global setting not enabled"
msgstr "不允许使用可重复使用的连接令牌,未启用全局设置" msgstr "不允许使用可重复使用的连接令牌,未启用全局设置"
#: authentication/api/connection_token.py:301 #: authentication/api/connection_token.py:310
msgid "Anonymous account is not supported for this asset" msgid "Anonymous account is not supported for this asset"
msgstr "匿名账号不支持当前资产" msgstr "匿名账号不支持当前资产"
#: authentication/api/connection_token.py:320 #: authentication/api/connection_token.py:329
msgid "Account not found" msgid "Account not found"
msgstr "账号未找到" msgstr "账号未找到"
#: authentication/api/connection_token.py:323 #: authentication/api/connection_token.py:332
msgid "Permission expired" msgid "Permission expired"
msgstr "授权已过期" msgstr "授权已过期"
#: authentication/api/connection_token.py:337 #: authentication/api/connection_token.py:346
msgid "ACL action is reject: {}({})" msgid "ACL action is reject: {}({})"
msgstr "ACL 动作是拒绝: {}({})" msgstr "ACL 动作是拒绝: {}({})"
#: authentication/api/connection_token.py:341 #: authentication/api/connection_token.py:350
msgid "ACL action is review" msgid "ACL action is review"
msgstr "ACL 动作是复核" msgstr "ACL 动作是复核"
@ -3644,7 +3644,7 @@ msgid "Module"
msgstr "模块" msgstr "模块"
#: ops/models/adhoc.py:24 ops/models/celery.py:58 ops/models/job.py:97 #: ops/models/adhoc.py:24 ops/models/celery.py:58 ops/models/job.py:97
#: terminal/models/component/task.py:16 #: terminal/models/component/task.py:14
msgid "Args" msgid "Args"
msgstr "参数" msgstr "参数"
@ -3687,7 +3687,7 @@ msgstr "Celery 任务"
msgid "Can view task monitor" msgid "Can view task monitor"
msgstr "可以查看任务监控" msgstr "可以查看任务监控"
#: ops/models/celery.py:59 terminal/models/component/task.py:17 #: ops/models/celery.py:59 terminal/models/component/task.py:15
msgid "Kwargs" msgid "Kwargs"
msgstr "其它参数" msgstr "其它参数"
@ -3824,7 +3824,7 @@ msgstr "清理异常作业"
msgid "Task log" msgid "Task log"
msgstr "任务列表" msgstr "任务列表"
#: ops/templates/ops/celery_task_log.html:71 #: ops/templates/ops/celery_task_log.html:71 terminal/serializers/task.py:10
msgid "Task name" msgid "Task name"
msgstr "任务名称" msgstr "任务名称"
@ -4204,7 +4204,7 @@ msgid "My assets"
msgstr "我的资产" msgstr "我的资产"
#: rbac/tree.py:56 terminal/models/applet/applet.py:51 #: rbac/tree.py:56 terminal/models/applet/applet.py:51
#: terminal/models/applet/applet.py:244 terminal/models/applet/host.py:29 #: terminal/models/applet/applet.py:246 terminal/models/applet/host.py:29
#: terminal/serializers/applet.py:15 #: terminal/serializers/applet.py:15
msgid "Applet" msgid "Applet"
msgstr "远程应用" msgstr "远程应用"
@ -5481,13 +5481,13 @@ msgstr "过期。"
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your password has expired, please click <a " " Your password has expired, please click <a href="
"href=\"%(user_password_update_url)s\"> this link </a> update password.\n" "\"%(user_password_update_url)s\"> this link </a> update password.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" 您的密码已经过期,请点击 <a " " 您的密码已经过期,请点击 <a href="
"href=\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n" "\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n"
" " " "
#: templates/_message.html:30 #: templates/_message.html:30
@ -5511,8 +5511,8 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your information was incomplete. Please click <a " " Your information was incomplete. Please click <a href="
"href=\"%(first_login_url)s\"> this link </a>to complete your information.\n" "\"%(first_login_url)s\"> this link </a>to complete your information.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
@ -5524,13 +5524,13 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" Your ssh public key not set or expired. Please click <a " " Your ssh public key not set or expired. Please click <a href="
"href=\"%(user_pubkey_update)s\"> this link </a>to update\n" "\"%(user_pubkey_update)s\"> this link </a>to update\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
" 您的SSH密钥没有设置或已失效请点击 <a " " 您的SSH密钥没有设置或已失效请点击 <a href="
"href=\"%(user_pubkey_update)s\"> 链接 </a> 更新\n" "\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" " " "
#: templates/_mfa_login_field.html:28 #: templates/_mfa_login_field.html:28
@ -5718,6 +5718,18 @@ msgstr "只读"
msgid "Writable" msgid "Writable"
msgstr "读写" msgstr "读写"
#: terminal/const.py:94
msgid "Kill Session"
msgstr "终断会话"
#: terminal/const.py:95
msgid "Lock Session"
msgstr "锁定会话"
#: terminal/const.py:96
msgid "Unlock Session"
msgstr "解锁会话"
#: terminal/exceptions.py:8 #: terminal/exceptions.py:8
msgid "Bulk create not support" msgid "Bulk create not support"
msgstr "不支持批量创建" msgstr "不支持批量创建"
@ -5770,7 +5782,7 @@ msgstr "只支持自定义平台"
msgid "Missing type in platform.yml" msgid "Missing type in platform.yml"
msgstr "在 platform.yml 中缺少类型" msgstr "在 platform.yml 中缺少类型"
#: terminal/models/applet/applet.py:246 terminal/models/applet/host.py:35 #: terminal/models/applet/applet.py:248 terminal/models/applet/host.py:35
#: terminal/models/applet/host.py:136 #: terminal/models/applet/host.py:136
msgid "Hosting" msgid "Hosting"
msgstr "宿主机" msgstr "宿主机"
@ -5941,23 +5953,23 @@ msgstr "回放"
msgid "Date end" msgid "Date end"
msgstr "结束日期" msgstr "结束日期"
#: terminal/models/session/session.py:240 #: terminal/models/session/session.py:261
msgid "Session record" msgid "Session record"
msgstr "会话记录" msgstr "会话记录"
#: terminal/models/session/session.py:242 #: terminal/models/session/session.py:263
msgid "Can monitor session" msgid "Can monitor session"
msgstr "可以监控会话" msgstr "可以监控会话"
#: terminal/models/session/session.py:243 #: terminal/models/session/session.py:264
msgid "Can share session" msgid "Can share session"
msgstr "可以分享会话" msgstr "可以分享会话"
#: terminal/models/session/session.py:244 #: terminal/models/session/session.py:265
msgid "Can terminate session" msgid "Can terminate session"
msgstr "可以终断会话" msgstr "可以终断会话"
#: terminal/models/session/session.py:245 #: terminal/models/session/session.py:266
msgid "Can validate session action perm" msgid "Can validate session action perm"
msgstr "可以验证会话动作权限" msgstr "可以验证会话动作权限"
@ -6124,8 +6136,10 @@ msgid ""
"has a private account, the other is public, when the application does not " "has a private account, the other is public, when the application does not "
"support multiple open and the special has been used, the public account will " "support multiple open and the special has been used, the public account will "
"be used to connect" "be used to connect"
msgstr "这些账号用于连接发布的应用,账号现在分为两种类型,一种是专用的,每个用户都有一个专用账号。 " msgstr ""
"另一种是公共的,当应用不支持多开且专用的已经被使用时,会使用公共账号连接" "这些账号用于连接发布的应用,账号现在分为两种类型,一种是专用的,每个用户都有"
"一个专用账号。 另一种是公共的,当应用不支持多开且专用的已经被使用时,会使用公"
"共账号连接"
#: terminal/serializers/applet_host.py:77 #: terminal/serializers/applet_host.py:77
msgid "The number of public accounts created automatically" msgid "The number of public accounts created automatically"
@ -6297,6 +6311,10 @@ msgstr "索引"
msgid "Doc type" msgid "Doc type"
msgstr "文档类型" msgstr "文档类型"
#: terminal/serializers/task.py:9
msgid "Session id"
msgstr "会话 ID"
#: terminal/serializers/terminal.py:83 terminal/serializers/terminal.py:91 #: terminal/serializers/terminal.py:83 terminal/serializers/terminal.py:91
msgid "Not found" msgid "Not found"
msgstr "没有发现" msgstr "没有发现"

View File

@ -3,17 +3,20 @@
import logging import logging
from rest_framework import status from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from common.api import JMSBulkModelViewSet from common.api import JMSBulkModelViewSet
from common.const.http import POST
from common.utils import get_object_or_none from common.utils import get_object_or_none
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from terminal import serializers from terminal import serializers
from terminal.const import TaskNameType
from terminal.models import Session, Task from terminal.models import Session, Task
from terminal.utils import is_session_approver from terminal.utils import is_session_approver
__all__ = ['TaskViewSet', 'KillSessionAPI', 'KillSessionForTicketAPI'] __all__ = ['TaskViewSet', 'KillSessionAPI', 'KillSessionForTicketAPI', ]
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@ -21,9 +24,44 @@ class TaskViewSet(JMSBulkModelViewSet):
queryset = Task.objects.all() queryset = Task.objects.all()
serializer_class = serializers.TaskSerializer serializer_class = serializers.TaskSerializer
filterset_fields = ('is_finished',) filterset_fields = ('is_finished',)
serializer_classes = {
'create_toggle_task': serializers.LockTaskSessionSerializer,
'handle_ticket_task': serializers.LockTaskSessionSerializer,
'default': serializers.TaskSerializer
}
rbac_perms = {
"create_toggle_task": "terminal.terminate_session",
}
@action(methods=[POST], detail=False, url_path='toggle-lock-session')
def create_toggle_task(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
session_id = serializer.validated_data['session_id']
task_name = serializer.validated_data['task_name']
session_ids = [session_id, ]
validated_session = create_sessions_tasks(session_ids, request.user, task_name=task_name)
return Response({"ok": validated_session})
@action(methods=[POST], detail=False, permission_classes=[IsAuthenticated, ],
url_path='toggle-lock-session-for-ticket', )
def handle_ticket_task(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
session_id = serializer.validated_data['session_id']
task_name = serializer.validated_data['task_name']
session_ids = [session_id, ]
user_id = request.user.id
for session_id in session_ids:
if not is_session_approver(session_id, user_id):
return Response({}, status=status.HTTP_403_FORBIDDEN)
with tmp_to_root_org():
validated_session = create_sessions_tasks(session_ids, request.user, task_name=task_name)
return Response({"ok": validated_session})
def kill_sessions(session_ids, user): def create_sessions_tasks(session_ids, user, task_name=TaskNameType.kill_session):
validated_session = [] validated_session = []
for session_id in session_ids: for session_id in session_ids:
@ -31,9 +69,10 @@ def kill_sessions(session_ids, user):
if session and not session.is_finished: if session and not session.is_finished:
validated_session.append(session_id) validated_session.append(session_id)
Task.objects.create( Task.objects.create(
name="kill_session", args=session.id, terminal=session.terminal, name=task_name, args=session.id, terminal=session.terminal,
kwargs={ kwargs={
'terminated_by': str(user) 'terminated_by': str(user),
'created_by': str(user)
} }
) )
return validated_session return validated_session
@ -47,7 +86,7 @@ class KillSessionAPI(APIView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
session_ids = request.data session_ids = request.data
validated_session = kill_sessions(session_ids, request.user) validated_session = create_sessions_tasks(session_ids, request.user)
return Response({"ok": validated_session}) return Response({"ok": validated_session})
@ -63,6 +102,6 @@ class KillSessionForTicketAPI(APIView):
return Response({}, status=status.HTTP_403_FORBIDDEN) return Response({}, status=status.HTTP_403_FORBIDDEN)
with tmp_to_root_org(): with tmp_to_root_org():
validated_session = kill_sessions(session_ids, request.user) validated_session = create_sessions_tasks(session_ids, request.user)
return Response({"ok": validated_session}) return Response({"ok": validated_session})

View File

@ -88,3 +88,9 @@ class SessionType(TextChoices):
class ActionPermission(TextChoices): class ActionPermission(TextChoices):
readonly = "readonly", _('Read Only') readonly = "readonly", _('Read Only')
writable = "writable", _('Writable') writable = "writable", _('Writable')
class TaskNameType(TextChoices):
kill_session = "kill_session", _('Kill Session')
lock_session = "lock_session", _('Lock Session')
unlock_session = "unlock_session", _('Unlock Session')

View File

@ -4,15 +4,13 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel from common.db.models import JMSBaseModel
from terminal.const import TaskNameType as SessionTaskChoices
from .terminal import Terminal from .terminal import Terminal
class Task(JMSBaseModel): class Task(JMSBaseModel):
NAME_CHOICES = (
("kill_session", "Kill Session"),
)
name = models.CharField(max_length=128, choices=NAME_CHOICES, verbose_name=_("Name")) name = models.CharField(max_length=128, choices=SessionTaskChoices.choices, verbose_name=_("Name"))
args = models.CharField(max_length=1024, verbose_name=_("Args")) args = models.CharField(max_length=1024, verbose_name=_("Args"))
kwargs = models.JSONField(default=dict, verbose_name=_("Kwargs")) kwargs = models.JSONField(default=dict, verbose_name=_("Kwargs"))
terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL) terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL)

View File

@ -48,6 +48,7 @@ class Session(OrgModelMixin):
upload_to = 'replay' upload_to = 'replay'
ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}' ACTIVE_CACHE_KEY_PREFIX = 'SESSION_ACTIVE_{}'
LOCK_CACHE_KEY_PREFIX = 'TOGGLE_LOCKED_SESSION_{}'
SUFFIX_MAP = {1: '.gz', 2: '.replay.gz', 3: '.cast.gz', 4: '.replay.mp4'} SUFFIX_MAP = {1: '.gz', 2: '.replay.gz', 3: '.cast.gz', 4: '.replay.mp4'}
DEFAULT_SUFFIXES = ['.replay.gz', '.cast.gz', '.gz', '.replay.mp4'] DEFAULT_SUFFIXES = ['.replay.gz', '.cast.gz', '.gz', '.replay.mp4']
@ -145,6 +146,26 @@ class Session(OrgModelMixin):
else: else:
return True return True
@property
def is_locked(self):
if self.is_finished:
return False
key = self.LOCK_CACHE_KEY_PREFIX.format(self.id)
return bool(cache.get(key))
@classmethod
def lock_session(cls, session_id):
key = cls.LOCK_CACHE_KEY_PREFIX.format(session_id)
# 会话锁定时间为 None表示永不过期
# You can set TIMEOUT to None so that, by default, cache keys never expire.
# https://docs.djangoproject.com/en/4.1/topics/cache/
cache.set(key, True, timeout=None)
@classmethod
def unlock_session(cls, session_id):
key = cls.LOCK_CACHE_KEY_PREFIX.format(session_id)
cache.delete(key)
@lazyproperty @lazyproperty
def terminal_display(self): def terminal_display(self):
display = self.terminal.name if self.terminal else '' display = self.terminal.name if self.terminal else ''

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .applet import * from .applet import *
from .command import *
from .session import *
from .storage import *
from .sharing import *
from .terminal import *
from .endpoint import *
from .applet_host import * from .applet_host import *
from .command import *
from .endpoint import *
from .session import *
from .sharing import *
from .storage import *
from .task import *
from .terminal import *

View File

@ -30,7 +30,7 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
"user", "asset", "user_id", "asset_id", 'account', 'account_id', "user", "asset", "user_id", "asset_id", 'account', 'account_id',
"protocol", 'type', "login_from", "remote_addr", "protocol", 'type', "login_from", "remote_addr",
"is_success", "is_finished", "has_replay", "has_command", "is_success", "is_finished", "has_replay", "has_command",
"date_start", "date_end", "comment", "terminal_display", "date_start", "date_end", "comment", "terminal_display", "is_locked",
'command_amount', 'command_amount',
] ]
fields_fk = ["terminal", ] fields_fk = ["terminal", ]

View File

@ -0,0 +1,10 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from terminal.const import TaskNameType as SessionTaskChoices
class LockTaskSessionSerializer(serializers.Serializer):
session_id = serializers.CharField(max_length=40, label=_('Session id'))
task_name = serializers.ChoiceField(choices=SessionTaskChoices.choices, label=_('Task name'))

View File

@ -1,5 +1,5 @@
from .applet import * from .applet import *
from .db_port import * from .db_port import *
from .terminal import *
from .session_sharing import *
from .session import * from .session import *
from .session_sharing import *
from .terminal import *

View File

@ -1,4 +1,4 @@
from django.db.models.signals import pre_save from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver from django.dispatch import receiver
from terminal.models import Session from terminal.models import Session
@ -8,3 +8,11 @@ from terminal.models import Session
def on_session_pre_save(sender, instance, **kwargs): def on_session_pre_save(sender, instance, **kwargs):
if instance.need_update_cmd_amount: if instance.need_update_cmd_amount:
instance.cmd_amount = instance.compute_command_amount() instance.cmd_amount = instance.compute_command_amount()
@receiver(post_save, sender=Session)
def on_session_finished(sender, instance: Session, created, **kwargs):
if not instance.is_finished:
return
# 清理一次可能因 task 未执行的缓存数据
Session.unlock_session(instance.id)

View File

@ -5,7 +5,8 @@ from django.utils.functional import LazyObject
from common.decorators import on_transaction_commit from common.decorators import on_transaction_commit
from common.utils import get_logger from common.utils import get_logger
from common.utils.connection import RedisPubSub from common.utils.connection import RedisPubSub
from ..models import Task from ..const import TaskNameType
from ..models import Task, Session
from ..utils import DBPortManager from ..utils import DBPortManager
db_port_manager: DBPortManager db_port_manager: DBPortManager
@ -24,7 +25,17 @@ component_event_chan = ComponentEventChan()
@receiver(post_save, sender=Task) @receiver(post_save, sender=Task)
@on_transaction_commit @on_transaction_commit
def on_task_created(sender, instance: Task, created, **kwargs): def on_task_created(sender, instance: Task, created, **kwargs):
if not created: if not created and instance.is_finished:
# 当组件完成 task 时,修改 session 的 lock 状态
session_id = instance.args
name = instance.name
if name == TaskNameType.lock_session:
Session.lock_session(session_id)
elif name == TaskNameType.unlock_session:
Session.unlock_session(session_id)
logger.debug(f"signal task post save: {instance.name}, "
f"session: {session_id}, "
f"is_finished: {instance.is_finished}")
return return
event = { event = {
"type": instance.name, "type": instance.name,

View File

@ -2,10 +2,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.urls import path, re_path from django.urls import path
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from common import api as capi
from .. import api from .. import api
app_name = 'terminal' app_name = 'terminal'

View File

@ -7,8 +7,8 @@ from rest_framework.renderers import JSONRenderer
from common.db.utils import safe_db_connection from common.db.utils import safe_db_connection
from common.utils import get_logger from common.utils import get_logger
from common.utils.connection import Subscription from common.utils.connection import Subscription
from terminal.models import Terminal from terminal.const import TaskNameType
from terminal.models import Session from terminal.models import Session, Terminal
from terminal.serializers import TaskSerializer, StatSerializer from terminal.serializers import TaskSerializer, StatSerializer
from .signal_handlers import component_event_chan from .signal_handlers import component_event_chan
@ -45,7 +45,7 @@ class TerminalTaskWebsocket(JsonWebsocketConsumer):
with safe_db_connection(): with safe_db_connection():
serializer.save() serializer.save()
def send_kill_tasks_msg(self, task_id=None): def send_tasks_msg(self, task_id=None):
content = self.get_terminal_tasks(task_id) content = self.get_terminal_tasks(task_id)
self.send(bytes_data=content) self.send(bytes_data=content)
@ -60,7 +60,7 @@ class TerminalTaskWebsocket(JsonWebsocketConsumer):
def watch_component_event(self): def watch_component_event(self):
# 先发一次已有的任务 # 先发一次已有的任务
self.send_kill_tasks_msg() self.send_tasks_msg()
ws = self ws = self
@ -68,8 +68,8 @@ class TerminalTaskWebsocket(JsonWebsocketConsumer):
logger.debug('New component task msg recv: {}'.format(msg)) logger.debug('New component task msg recv: {}'.format(msg))
msg_type = msg.get('type') msg_type = msg.get('type')
payload = msg.get('payload') payload = msg.get('payload')
if msg_type == "kill_session": if msg_type in TaskNameType.names:
ws.send_kill_tasks_msg(payload.get('id')) ws.send_tasks_msg(payload.get('id'))
return component_event_chan.subscribe(handle_task_msg_recv) return component_event_chan.subscribe(handle_task_msg_recv)