mirror of https://github.com/jumpserver/jumpserver
feat: Supports running adhoc,playbook with variable (#14417)
* perf:Create a job that supports adding node parameters * feat: add variable model * feat: Modify Variable and AdHoc models, * feat: Parameters can be set when running job * feat: Supports setting variable type * feat: Supports running adhoc with parameters * feat: Supports running playbook with parameters * fix: Translate * feat: Support setting variables for scheduled tasks * perf: Translate --------- Co-authored-by: wangruidong <940853815@qq.com>pull/14418/head
parent
8f11167db0
commit
c96ae1022b
|
@ -187,7 +187,7 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
|
|||
'PermedAsset', 'PermedAccount', 'MenuPermission',
|
||||
'Permission', 'TicketSession', 'ApplyLoginTicket',
|
||||
'ApplyCommandTicket', 'ApplyLoginAssetTicket',
|
||||
'FavoriteAsset', 'ChangeSecretRecord', 'AppProvider',
|
||||
'FavoriteAsset', 'ChangeSecretRecord', 'AppProvider', 'Variable'
|
||||
}
|
||||
include_models = {'UserSession'}
|
||||
for i, app in enumerate(apps.get_models(), 1):
|
||||
|
|
|
@ -5098,11 +5098,11 @@ msgstr "パラメータ定義"
|
|||
|
||||
#: ops/models/job.py:155
|
||||
msgid "Run as"
|
||||
msgstr "ユーザーとして実行"
|
||||
msgstr "実行アカウント (じっこうアカウント)"
|
||||
|
||||
#: ops/models/job.py:157
|
||||
msgid "Run as policy"
|
||||
msgstr "ユーザー ポリシー"
|
||||
msgstr "アカウントポリシー "
|
||||
|
||||
#: ops/models/job.py:222 ops/serializers/job.py:92
|
||||
#: terminal/notifications.py:182
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-11-06 11:27+0800\n"
|
||||
"POT-Creation-Date: 2024-11-06 16:37+0800\n"
|
||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
|
@ -35,7 +35,7 @@ msgstr "生成资产或应用相关备份信息文件"
|
|||
|
||||
#: accounts/automations/backup_account/handlers.py:156
|
||||
#: accounts/automations/backup_account/handlers.py:295
|
||||
#: accounts/automations/backup_account/manager.py:40 ops/serializers/job.py:76
|
||||
#: accounts/automations/backup_account/manager.py:40 ops/serializers/job.py:82
|
||||
#: settings/templates/ldap/_msg_import_ldap_user.html:7
|
||||
msgid "Time cost"
|
||||
msgstr "花费时间"
|
||||
|
@ -467,7 +467,7 @@ msgstr "账号备份计划"
|
|||
|
||||
#: accounts/models/automations/backup_account.py:120
|
||||
#: assets/models/automations/base.py:115 audits/models.py:65
|
||||
#: ops/models/base.py:55 ops/models/celery.py:89 ops/models/job.py:242
|
||||
#: ops/models/base.py:55 ops/models/celery.py:89 ops/models/job.py:247
|
||||
#: ops/templates/ops/celery_task_log.html:101
|
||||
#: perms/models/asset_permission.py:78 settings/serializers/feature.py:25
|
||||
#: settings/templates/ldap/_msg_import_ldap_user.html:5
|
||||
|
@ -506,7 +506,7 @@ msgstr "原因"
|
|||
#: accounts/models/automations/backup_account.py:136
|
||||
#: accounts/serializers/automations/change_secret.py:117
|
||||
#: accounts/serializers/automations/change_secret.py:152
|
||||
#: ops/serializers/job.py:74 terminal/serializers/session.py:54
|
||||
#: ops/serializers/job.py:80 terminal/serializers/session.py:54
|
||||
msgid "Is success"
|
||||
msgstr "是否成功"
|
||||
|
||||
|
@ -581,7 +581,7 @@ msgstr "开始日期"
|
|||
|
||||
#: accounts/models/automations/change_secret.py:42
|
||||
#: assets/models/automations/base.py:116 ops/models/base.py:56
|
||||
#: ops/models/celery.py:90 ops/models/job.py:243
|
||||
#: ops/models/celery.py:90 ops/models/job.py:248
|
||||
#: terminal/models/applet/host.py:142
|
||||
msgid "Date finished"
|
||||
msgstr "结束日期"
|
||||
|
@ -589,7 +589,7 @@ msgstr "结束日期"
|
|||
#: accounts/models/automations/change_secret.py:44
|
||||
#: assets/models/automations/base.py:113
|
||||
#: assets/serializers/automations/base.py:39 audits/models.py:208
|
||||
#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:234
|
||||
#: audits/serializers.py:54 ops/models/base.py:49 ops/models/job.py:239
|
||||
#: terminal/models/applet/applet.py:331 terminal/models/applet/host.py:140
|
||||
#: terminal/models/component/status.py:30
|
||||
#: terminal/models/virtualapp/virtualapp.py:99
|
||||
|
@ -664,8 +664,8 @@ msgstr "触发方式"
|
|||
#: audits/models.py:92 audits/serializers.py:84
|
||||
#: authentication/serializers/connect_token_secret.py:119
|
||||
#: authentication/templates/authentication/_access_key_modal.html:34
|
||||
#: behemoth/serializers/environment.py:13 perms/serializers/permission.py:52
|
||||
#: perms/serializers/permission.py:74 tickets/serializers/ticket/ticket.py:21
|
||||
#: perms/serializers/permission.py:52 perms/serializers/permission.py:74
|
||||
#: tickets/serializers/ticket/ticket.py:21
|
||||
msgid "Action"
|
||||
msgstr "动作"
|
||||
|
||||
|
@ -718,8 +718,8 @@ msgstr "密码规则"
|
|||
#: authentication/serializers/connect_token_secret.py:113
|
||||
#: authentication/serializers/connect_token_secret.py:169 labels/models.py:11
|
||||
#: ops/mixin.py:28 ops/models/adhoc.py:19 ops/models/celery.py:15
|
||||
#: ops/models/celery.py:81 ops/models/job.py:142 ops/models/playbook.py:30
|
||||
#: ops/serializers/job.py:18 orgs/models.py:82
|
||||
#: ops/models/celery.py:81 ops/models/job.py:145 ops/models/playbook.py:28
|
||||
#: ops/models/variable.py:9 ops/serializers/job.py:19 orgs/models.py:82
|
||||
#: perms/models/asset_permission.py:61 rbac/models/role.py:29
|
||||
#: rbac/serializers/role.py:28 settings/models.py:35 settings/models.py:184
|
||||
#: settings/serializers/msg.py:89 settings/serializers/terminal.py:9
|
||||
|
@ -882,7 +882,7 @@ msgstr "类别"
|
|||
#: assets/serializers/asset/common.py:146 assets/serializers/platform.py:159
|
||||
#: assets/serializers/platform.py:171 audits/serializers.py:53
|
||||
#: audits/serializers.py:170
|
||||
#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:150
|
||||
#: authentication/serializers/connect_token_secret.py:126 ops/models/job.py:153
|
||||
#: perms/serializers/user_permission.py:27 terminal/models/applet/applet.py:40
|
||||
#: terminal/models/component/storage.py:58
|
||||
#: terminal/models/component/storage.py:152 terminal/serializers/applet.py:29
|
||||
|
@ -918,10 +918,8 @@ msgstr "已修改"
|
|||
#: assets/models/automations/base.py:19
|
||||
#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:34
|
||||
#: assets/serializers/platform.py:180 assets/serializers/platform.py:212
|
||||
#: authentication/api/connection_token.py:410
|
||||
#: behemoth/serializers/environment.py:11
|
||||
#: behemoth/serializers/environment.py:22 ops/models/base.py:17
|
||||
#: ops/models/job.py:152 ops/serializers/job.py:19
|
||||
#: authentication/api/connection_token.py:410 ops/models/base.py:17
|
||||
#: ops/models/job.py:155 ops/serializers/job.py:20
|
||||
#: perms/serializers/permission.py:46
|
||||
#: terminal/templates/terminal/_msg_command_execute_alert.html:16
|
||||
#: xpack/plugins/cloud/manager.py:89
|
||||
|
@ -1071,7 +1069,7 @@ msgstr "关联平台,可配置推送参数,如果不关联,将使用默认
|
|||
|
||||
#: accounts/serializers/account/virtual.py:19 assets/models/cmd_filter.py:40
|
||||
#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:25
|
||||
#: ops/models/job.py:158 ops/models/playbook.py:33 rbac/models/role.py:37
|
||||
#: ops/models/job.py:163 ops/models/playbook.py:31 rbac/models/role.py:37
|
||||
#: settings/models.py:40 terminal/models/applet/applet.py:46
|
||||
#: terminal/models/applet/applet.py:332 terminal/models/applet/host.py:143
|
||||
#: terminal/models/component/endpoint.py:26
|
||||
|
@ -1095,7 +1093,8 @@ msgstr ""
|
|||
|
||||
#: accounts/serializers/automations/base.py:23
|
||||
#: assets/models/asset/common.py:176 assets/serializers/asset/common.py:172
|
||||
#: assets/serializers/automations/base.py:21 perms/serializers/permission.py:47
|
||||
#: assets/serializers/automations/base.py:21 ops/serializers/job.py:21
|
||||
#: perms/serializers/permission.py:47
|
||||
msgid "Nodes"
|
||||
msgstr "节点"
|
||||
|
||||
|
@ -1396,7 +1395,7 @@ msgid "Accounts"
|
|||
msgstr "账号"
|
||||
|
||||
#: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60
|
||||
#: ops/serializers/job.py:73 terminal/const.py:86
|
||||
#: ops/serializers/job.py:79 terminal/const.py:86
|
||||
#: terminal/models/session/session.py:43 terminal/serializers/command.py:18
|
||||
#: terminal/templates/terminal/_msg_command_alert.html:12
|
||||
#: terminal/templates/terminal/_msg_command_execute_alert.html:10
|
||||
|
@ -2032,13 +2031,14 @@ msgid "Proxy"
|
|||
msgstr "代理"
|
||||
|
||||
#: assets/models/automations/base.py:18 assets/models/cmd_filter.py:32
|
||||
#: assets/models/node.py:553 perms/models/asset_permission.py:72
|
||||
#: tickets/models/ticket/apply_asset.py:14 xpack/plugins/cloud/models.py:388
|
||||
#: assets/models/node.py:553 ops/models/job.py:156
|
||||
#: perms/models/asset_permission.py:72 tickets/models/ticket/apply_asset.py:14
|
||||
#: xpack/plugins/cloud/models.py:388
|
||||
msgid "Node"
|
||||
msgstr "节点"
|
||||
|
||||
#: assets/models/automations/base.py:22 ops/models/job.py:237
|
||||
#: settings/serializers/auth/sms.py:108
|
||||
#: assets/models/automations/base.py:22 ops/models/job.py:242
|
||||
#: ops/serializers/job.py:23 settings/serializers/auth/sms.py:108
|
||||
msgid "Parameters"
|
||||
msgstr "参数"
|
||||
|
||||
|
@ -2053,7 +2053,7 @@ msgstr "资产自动化任务"
|
|||
# msgid "Comment"
|
||||
# msgstr "备注"
|
||||
#: assets/models/automations/base.py:114 assets/models/cmd_filter.py:41
|
||||
#: common/db/models.py:34 ops/models/base.py:54 ops/models/job.py:241
|
||||
#: common/db/models.py:34 ops/models/base.py:54 ops/models/job.py:246
|
||||
#: users/models/user/__init__.py:311
|
||||
msgid "Date created"
|
||||
msgstr "创建日期"
|
||||
|
@ -2185,7 +2185,7 @@ msgstr "可以匹配节点"
|
|||
msgid "Primary"
|
||||
msgstr "主要的"
|
||||
|
||||
#: assets/models/platform.py:18
|
||||
#: assets/models/platform.py:18 ops/models/variable.py:20
|
||||
msgid "Required"
|
||||
msgstr "必须的"
|
||||
|
||||
|
@ -2728,7 +2728,7 @@ msgstr "标签"
|
|||
msgid "operate_log_id"
|
||||
msgstr "操作日志ID"
|
||||
|
||||
#: audits/backends/db.py:111
|
||||
#: audits/backends/db.py:111 ops/models/variable.py:19
|
||||
msgid "Tips"
|
||||
msgstr "提示"
|
||||
|
||||
|
@ -2972,8 +2972,8 @@ msgid "Offline user session"
|
|||
msgstr "下线用户会话"
|
||||
|
||||
#: audits/serializers.py:33 ops/models/adhoc.py:24 ops/models/base.py:16
|
||||
#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:151
|
||||
#: ops/models/job.py:240 ops/models/playbook.py:32
|
||||
#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:154
|
||||
#: ops/models/job.py:245 ops/models/playbook.py:30 ops/models/variable.py:17
|
||||
#: terminal/models/session/sharing.py:25
|
||||
msgid "Creator"
|
||||
msgstr "创建者"
|
||||
|
@ -4664,7 +4664,7 @@ msgid ""
|
|||
" work orders, and other notifications"
|
||||
msgstr "系统一些告警,工单等需要发送站内信时执行该任务"
|
||||
|
||||
#: ops/ansible/inventory.py:116 ops/models/job.py:65
|
||||
#: ops/ansible/inventory.py:116 ops/models/job.py:68
|
||||
msgid "No account available"
|
||||
msgstr "无可用账号"
|
||||
|
||||
|
@ -4688,34 +4688,34 @@ msgstr "任务 {} 不存在"
|
|||
msgid "Task {} args or kwargs error"
|
||||
msgstr "任务 {} 执行参数错误"
|
||||
|
||||
#: ops/api/job.py:83
|
||||
#: ops/api/job.py:68
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Asset ({asset}) must have at least one of the following protocols added: "
|
||||
"SSH, SFTP, or WinRM"
|
||||
msgstr "资产({asset})至少要添加ssh,sftp,winrm其中一种协议"
|
||||
|
||||
#: ops/api/job.py:84
|
||||
#: ops/api/job.py:69
|
||||
#, python-brace-format
|
||||
msgid "Asset ({asset}) authorization is missing SSH, SFTP, or WinRM protocol"
|
||||
msgstr "资产({asset})授权缺少ssh,sftp或winrm协议"
|
||||
|
||||
#: ops/api/job.py:85
|
||||
#: ops/api/job.py:70
|
||||
#, python-brace-format
|
||||
msgid "Asset ({asset}) authorization lacks upload permissions"
|
||||
msgstr "资产({asset})授权缺少上传权限"
|
||||
|
||||
#: ops/api/job.py:170
|
||||
#: ops/api/job.py:157
|
||||
msgid "Duplicate file exists"
|
||||
msgstr "存在同名文件"
|
||||
|
||||
#: ops/api/job.py:175
|
||||
#: ops/api/job.py:162
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"File size exceeds maximum limit. Please select a file smaller than {limit}MB"
|
||||
msgstr "文件大小超过最大限制。请选择小于 {limit}MB 的文件。"
|
||||
|
||||
#: ops/api/job.py:244
|
||||
#: ops/api/job.py:231
|
||||
msgid ""
|
||||
"The task is being created and cannot be interrupted. Please try again later."
|
||||
msgstr "正在创建任务,无法中断,请稍后重试。"
|
||||
|
@ -4784,11 +4784,13 @@ msgstr "空白"
|
|||
msgid "VCS"
|
||||
msgstr "VCS"
|
||||
|
||||
#: ops/const.py:38 ops/models/adhoc.py:44 settings/serializers/feature.py:123
|
||||
#: ops/const.py:38 ops/models/adhoc.py:44 ops/models/variable.py:26
|
||||
#: settings/serializers/feature.py:123
|
||||
msgid "Adhoc"
|
||||
msgstr "命令"
|
||||
|
||||
#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:91
|
||||
#: ops/const.py:39 ops/models/job.py:152 ops/models/playbook.py:89
|
||||
#: ops/models/variable.py:23
|
||||
msgid "Playbook"
|
||||
msgstr "Playbook"
|
||||
|
||||
|
@ -4861,6 +4863,14 @@ msgstr "公有"
|
|||
msgid "Private"
|
||||
msgstr "私有"
|
||||
|
||||
#: ops/const.py:91
|
||||
msgid "Text"
|
||||
msgstr "文本框"
|
||||
|
||||
#: ops/const.py:92
|
||||
msgid "Select"
|
||||
msgstr "选择框"
|
||||
|
||||
#: ops/exception.py:6
|
||||
msgid "no valid program entry found."
|
||||
msgstr "没有可用程序入口"
|
||||
|
@ -4900,16 +4910,16 @@ msgstr "需要周期或定期设置"
|
|||
msgid "Pattern"
|
||||
msgstr "模式"
|
||||
|
||||
#: ops/models/adhoc.py:22 ops/models/job.py:146
|
||||
#: ops/models/adhoc.py:22 ops/models/job.py:149
|
||||
msgid "Module"
|
||||
msgstr "模块"
|
||||
|
||||
#: ops/models/adhoc.py:23 ops/models/celery.py:82 ops/models/job.py:144
|
||||
#: ops/models/adhoc.py:23 ops/models/celery.py:82 ops/models/job.py:147
|
||||
#: terminal/models/component/task.py:14
|
||||
msgid "Args"
|
||||
msgstr "参数"
|
||||
msgstr "内容"
|
||||
|
||||
#: ops/models/adhoc.py:26 ops/models/playbook.py:36 ops/serializers/mixin.py:10
|
||||
#: ops/models/adhoc.py:26 ops/models/playbook.py:34 ops/serializers/mixin.py:10
|
||||
#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
|
||||
#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
|
||||
msgid "Scope"
|
||||
|
@ -4923,16 +4933,16 @@ msgstr "账号策略"
|
|||
msgid "Last execution"
|
||||
msgstr "最后执行"
|
||||
|
||||
#: ops/models/base.py:22 ops/serializers/job.py:17
|
||||
#: ops/models/base.py:22 ops/serializers/job.py:18
|
||||
msgid "Date last run"
|
||||
msgstr "最后运行日期"
|
||||
|
||||
#: ops/models/base.py:51 ops/models/job.py:238
|
||||
#: ops/models/base.py:51 ops/models/job.py:243
|
||||
#: xpack/plugins/cloud/models.py:225
|
||||
msgid "Result"
|
||||
msgstr "结果"
|
||||
|
||||
#: ops/models/base.py:52 ops/models/job.py:239
|
||||
#: ops/models/base.py:52 ops/models/job.py:244
|
||||
#: xpack/plugins/cloud/manager.py:99
|
||||
msgid "Summary"
|
||||
msgstr "汇总"
|
||||
|
@ -4961,55 +4971,87 @@ msgstr "发布日期"
|
|||
msgid "Celery Task Execution"
|
||||
msgstr "Celery 任务执行"
|
||||
|
||||
#: ops/models/job.py:147
|
||||
#: ops/models/job.py:150
|
||||
msgid "Run dir"
|
||||
msgstr "运行目录"
|
||||
|
||||
#: ops/models/job.py:148
|
||||
#: ops/models/job.py:151
|
||||
msgid "Timeout (Seconds)"
|
||||
msgstr "超时时间 (秒)"
|
||||
|
||||
#: ops/models/job.py:153
|
||||
#: ops/models/job.py:157
|
||||
msgid "Use Parameter Define"
|
||||
msgstr "使用参数定义"
|
||||
|
||||
#: ops/models/job.py:154
|
||||
#: ops/models/job.py:158
|
||||
msgid "Parameters define"
|
||||
msgstr "参数定义"
|
||||
|
||||
#: ops/models/job.py:155
|
||||
#: ops/models/job.py:159
|
||||
msgid "Periodic variable"
|
||||
msgstr "定时任务参数"
|
||||
|
||||
#: ops/models/job.py:160
|
||||
msgid "Run as"
|
||||
msgstr "运行用户"
|
||||
msgstr "运行账号"
|
||||
|
||||
#: ops/models/job.py:157
|
||||
#: ops/models/job.py:162
|
||||
msgid "Run as policy"
|
||||
msgstr "用户策略"
|
||||
msgstr "账户策略"
|
||||
|
||||
#: ops/models/job.py:222 ops/serializers/job.py:92
|
||||
#: ops/models/job.py:227 ops/models/variable.py:28 ops/serializers/job.py:98
|
||||
#: terminal/notifications.py:182
|
||||
msgid "Job"
|
||||
msgstr "作业"
|
||||
|
||||
#: ops/models/job.py:245
|
||||
#: ops/models/job.py:250
|
||||
msgid "Material"
|
||||
msgstr "Material"
|
||||
|
||||
#: ops/models/job.py:247
|
||||
#: ops/models/job.py:252
|
||||
msgid "Material Type"
|
||||
msgstr "Material 类型"
|
||||
|
||||
#: ops/models/job.py:558
|
||||
#: ops/models/job.py:564
|
||||
msgid "Job Execution"
|
||||
msgstr "作业执行"
|
||||
|
||||
#: ops/models/playbook.py:35
|
||||
#: ops/models/playbook.py:33
|
||||
msgid "CreateMethod"
|
||||
msgstr "创建方式"
|
||||
|
||||
#: ops/models/playbook.py:37
|
||||
#: ops/models/playbook.py:35
|
||||
msgid "VCS URL"
|
||||
msgstr "VCS URL"
|
||||
|
||||
#: ops/models/variable.py:11
|
||||
msgid "Variable name"
|
||||
msgstr "参数名"
|
||||
|
||||
#: ops/models/variable.py:12
|
||||
msgid ""
|
||||
"The variable name used in the script has a fixed prefix 'jms_' followed by "
|
||||
"the input variable name. For example, if the variable name is 'name,' the "
|
||||
"final generated environment variable will be 'jms_name'."
|
||||
msgstr "在脚本使用的变量名称,固定前缀 jms_ + 输入的变量名,例如变量名name,则最终生成环境变量为 jms_name"
|
||||
|
||||
#: ops/models/variable.py:16
|
||||
msgid "Default Value"
|
||||
msgstr "默认值"
|
||||
|
||||
#: ops/models/variable.py:18
|
||||
msgid "Variable type"
|
||||
msgstr "变量名"
|
||||
|
||||
#: ops/models/variable.py:21 ops/serializers/variable.py:23
|
||||
msgid "ExtraVars"
|
||||
msgstr "额外参数"
|
||||
|
||||
#: ops/models/variable.py:49 ops/serializers/adhoc.py:16
|
||||
#: ops/serializers/job.py:22 ops/serializers/playbook.py:21
|
||||
msgid "Variable"
|
||||
msgstr "变量"
|
||||
|
||||
#: ops/notifications.py:20
|
||||
msgid "Server performance"
|
||||
msgstr "监控告警"
|
||||
|
@ -5046,61 +5088,72 @@ msgstr "周期执行"
|
|||
msgid "Next execution time"
|
||||
msgstr "下次执行时间"
|
||||
|
||||
#: ops/serializers/job.py:15
|
||||
#: ops/serializers/job.py:17
|
||||
msgid "Execute after saving"
|
||||
msgstr "保存后执行"
|
||||
|
||||
#: ops/serializers/job.py:52 terminal/serializers/session.py:49
|
||||
#: ops/serializers/job.py:58 terminal/serializers/session.py:49
|
||||
msgid "Duration"
|
||||
msgstr "时长"
|
||||
|
||||
#: ops/serializers/job.py:72
|
||||
#: ops/serializers/job.py:78
|
||||
msgid "Job type"
|
||||
msgstr "任务类型"
|
||||
|
||||
#: ops/serializers/job.py:75 terminal/serializers/session.py:58
|
||||
#: ops/serializers/job.py:81 terminal/serializers/session.py:58
|
||||
msgid "Is finished"
|
||||
msgstr "是否完成"
|
||||
|
||||
#: ops/serializers/job.py:89
|
||||
#: ops/serializers/job.py:95
|
||||
msgid "Task id"
|
||||
msgstr "任务 ID"
|
||||
|
||||
#: ops/serializers/job.py:98
|
||||
#: ops/serializers/job.py:104
|
||||
msgid "You do not have permission for the current job."
|
||||
msgstr "你没有当前作业的权限。"
|
||||
|
||||
#: ops/tasks.py:51
|
||||
#: ops/serializers/variable.py:20
|
||||
msgid "Variable Type"
|
||||
msgstr "参数类型"
|
||||
|
||||
#: ops/serializers/variable.py:25
|
||||
msgid ""
|
||||
"Each item is on a separate line, with each line separated by a colon. The "
|
||||
"part before the colon is the display content, and the part after the colon "
|
||||
"is the value."
|
||||
msgstr "每项单独一行,每行可以用英文冒号分割前边是显示的内容后边是值"
|
||||
|
||||
#: ops/tasks.py:52
|
||||
msgid "Run ansible task"
|
||||
msgstr "运行 Ansible 任务"
|
||||
|
||||
#: ops/tasks.py:54
|
||||
#: ops/tasks.py:55
|
||||
msgid ""
|
||||
"Execute scheduled adhoc and playbooks, periodically invoking the task for "
|
||||
"execution"
|
||||
msgstr "当执行定时的快捷命令,playbook,定时调用该任务执行"
|
||||
|
||||
#: ops/tasks.py:82
|
||||
#: ops/tasks.py:85
|
||||
msgid "Run ansible task execution"
|
||||
msgstr "开始执行 Ansible 任务"
|
||||
|
||||
#: ops/tasks.py:85
|
||||
#: ops/tasks.py:88
|
||||
msgid "Execute the task when manually adhoc or playbooks"
|
||||
msgstr "手动执行快捷命令,playbook时执行该任务"
|
||||
|
||||
#: ops/tasks.py:99
|
||||
#: ops/tasks.py:102
|
||||
msgid "Clear celery periodic tasks"
|
||||
msgstr "清理周期任务"
|
||||
|
||||
#: ops/tasks.py:101
|
||||
#: ops/tasks.py:104
|
||||
msgid "At system startup, clean up celery tasks that no longer exist"
|
||||
msgstr "系统启动时,清理已经不存在的celery任务"
|
||||
|
||||
#: ops/tasks.py:125
|
||||
#: ops/tasks.py:128
|
||||
msgid "Create or update periodic tasks"
|
||||
msgstr "创建或更新周期任务"
|
||||
|
||||
#: ops/tasks.py:127
|
||||
#: ops/tasks.py:130
|
||||
msgid ""
|
||||
"With version iterations, new tasks may be added, or task names and execution "
|
||||
"times may \n"
|
||||
|
@ -5111,11 +5164,11 @@ msgstr ""
|
|||
"随着版本迭代,可能会新增任务或者修改任务的名称,执行时间,所以在系统启动时,"
|
||||
"将会注册任务或者更新定时任务参数"
|
||||
|
||||
#: ops/tasks.py:140
|
||||
#: ops/tasks.py:143
|
||||
msgid "Periodic check service performance"
|
||||
msgstr "周期检测服务性能"
|
||||
|
||||
#: ops/tasks.py:142
|
||||
#: ops/tasks.py:145
|
||||
msgid ""
|
||||
"Check every hour whether each component is offline and whether the CPU, "
|
||||
"memory, \n"
|
||||
|
@ -5125,11 +5178,11 @@ msgstr ""
|
|||
"每小时检测各组件是否离线,cpu,内存,硬盘使用率是否超过阈值,向管理员发送消息"
|
||||
"预警"
|
||||
|
||||
#: ops/tasks.py:152
|
||||
#: ops/tasks.py:155
|
||||
msgid "Clean up unexpected jobs"
|
||||
msgstr "清理异常作业"
|
||||
|
||||
#: ops/tasks.py:154
|
||||
#: ops/tasks.py:157
|
||||
msgid ""
|
||||
"Due to exceptions caused by executing adhoc and playbooks in the Job "
|
||||
"Center, \n"
|
||||
|
@ -5142,11 +5195,11 @@ msgstr ""
|
|||
"由于作业中心执行快捷命令,playbook会产生异常,任务状态未更新完成,系统将每小"
|
||||
"时执行清理超3小时未完成的异常作业,并将任务标记失败"
|
||||
|
||||
#: ops/tasks.py:167
|
||||
#: ops/tasks.py:170
|
||||
msgid "Clean job_execution db record"
|
||||
msgstr "清理作业中心执行历史"
|
||||
|
||||
#: ops/tasks.py:169
|
||||
#: ops/tasks.py:172
|
||||
msgid ""
|
||||
"Due to the execution of adhoc and playbooks in the Job Center, execution "
|
||||
"records will \n"
|
||||
|
@ -9600,6 +9653,14 @@ msgstr "账号保护已开启,请根据提示完成以下操作"
|
|||
msgid "Open MFA Authenticator and enter the 6-bit dynamic code"
|
||||
msgstr "请打开 MFA 验证器,输入 6 位动态码"
|
||||
|
||||
#: users/utils.py:60
|
||||
msgid "Auth success"
|
||||
msgstr "认证成功"
|
||||
|
||||
#: users/utils.py:61
|
||||
msgid "Redirecting to JumpServer Client"
|
||||
msgstr ""
|
||||
|
||||
#: users/views/profile/otp.py:106
|
||||
msgid "Already bound"
|
||||
msgstr "已经绑定"
|
||||
|
|
|
@ -4982,11 +4982,11 @@ msgstr "參數定義"
|
|||
|
||||
#: ops/models/job.py:155
|
||||
msgid "Run as"
|
||||
msgstr "執行使用者"
|
||||
msgstr "運行賬號"
|
||||
|
||||
#: ops/models/job.py:157
|
||||
msgid "Run as policy"
|
||||
msgstr "使用者策略"
|
||||
msgstr "賬號策略"
|
||||
|
||||
#: ops/models/job.py:222 ops/serializers/job.py:92
|
||||
#: terminal/notifications.py:182
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
"AdhocCreate": "Create the command",
|
||||
"AdhocDetail": "Command details",
|
||||
"AdhocManage": "Script",
|
||||
"AdhocUpdate": "Update the command",
|
||||
"AdhocUpdate": "Update Script",
|
||||
"Advanced": "Advanced settings",
|
||||
"AfterChange": "After changes",
|
||||
"AjaxError404": "404 request error",
|
||||
|
@ -1401,5 +1401,12 @@
|
|||
"ZoneUpdate": "Update the zone",
|
||||
"disallowSelfUpdateFields": "Not allowed to modify the current fields yourself",
|
||||
"forceEnableMFAHelpText": "If force enable, user can not disable by themselves",
|
||||
"removeWarningMsg": "Are you sure you want to remove"
|
||||
"removeWarningMsg": "Are you sure you want to remove",
|
||||
"DefaultValue": "Default value",
|
||||
"AddVariable": "Add Variable",
|
||||
"VariableName": "Variable name",
|
||||
"LoadTemplate": "Load template",
|
||||
"Templates": "Templates",
|
||||
"ExtraArgsPlaceholder": "One option per line, for example:\nOption 1: Value 1\nOption 2: Value 2",
|
||||
"setVariable": "Set variable"
|
||||
}
|
|
@ -70,7 +70,7 @@
|
|||
"AdhocCreate": "アドホックコマンドを作成",
|
||||
"AdhocDetail": "コマンド詳細",
|
||||
"AdhocManage": "スクリプト管理",
|
||||
"AdhocUpdate": "コマンドを更新",
|
||||
"AdhocUpdate": "更新スクリプト",
|
||||
"Advanced": "高度な設定",
|
||||
"AfterChange": "変更後",
|
||||
"AjaxError404": "404 リクエストエラー",
|
||||
|
@ -1053,7 +1053,7 @@
|
|||
"Rules": "規則",
|
||||
"Run": "Action",
|
||||
"RunAgain": "再実行",
|
||||
"RunAs": "実行ユーザー",
|
||||
"RunAs": "実行アカウント (じっこうアカウント)",
|
||||
"RunCommand": "コマンドの実行",
|
||||
"RunJob": "ジョブを実行",
|
||||
"RunSucceed": "タスクが成功",
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
"AdhocCreate": "创建命令",
|
||||
"AdhocDetail": "命令详情",
|
||||
"AdhocManage": "脚本管理",
|
||||
"AdhocUpdate": "更新命令",
|
||||
"AdhocUpdate": "更新脚本",
|
||||
"Advanced": "高级设置",
|
||||
"AfterChange": "变更后",
|
||||
"AjaxError404": "404 请求错误",
|
||||
|
@ -1022,7 +1022,7 @@
|
|||
"Rules": "规则",
|
||||
"Run": "执行",
|
||||
"RunAgain": "再次执行",
|
||||
"RunAs": "运行用户",
|
||||
"RunAs": "运行账号",
|
||||
"RunCommand": "运行命令",
|
||||
"RunJob": "运行作业",
|
||||
"RunSucceed": "任务执行成功",
|
||||
|
@ -1405,5 +1405,13 @@
|
|||
"ZoneUpdate": "更新网域",
|
||||
"disallowSelfUpdateFields": "不允许自己修改当前字段",
|
||||
"forceEnableMFAHelpText": "如果强制启用,用户无法自行禁用",
|
||||
"removeWarningMsg": "你确定要移除"
|
||||
"removeWarningMsg": "你确定要移除",
|
||||
"VariableName": "变量名",
|
||||
"ExecuteAfterSaving": "保存后执行",
|
||||
"DefaultValue": "默认值",
|
||||
"AddVariable": "添加参数",
|
||||
"LoadTemplate": "从模板中加载",
|
||||
"Templates": "模板",
|
||||
"ExtraArgsPlaceholder": "每行一个选项,例如:\n选项1:值1\n选项2:值2\n",
|
||||
"setVariable": "设置参数"
|
||||
}
|
|
@ -88,7 +88,7 @@
|
|||
"AdhocCreate": "創建命令",
|
||||
"AdhocDetail": "命令詳情",
|
||||
"AdhocManage": "腳本管理",
|
||||
"AdhocUpdate": "更新命令",
|
||||
"AdhocUpdate": "更新腳本",
|
||||
"Admin": "管理員",
|
||||
"AdminUser": "特權用戶",
|
||||
"AdminUserCreate": "創建管理用戶",
|
||||
|
@ -1356,7 +1356,7 @@
|
|||
"Rules": "規則",
|
||||
"Run": "運行",
|
||||
"RunAgain": "再次執行",
|
||||
"RunAs": "執行使用者",
|
||||
"RunAs": "運行賬號",
|
||||
"RunCommand": "運行命令",
|
||||
"RunJob": "運行作業",
|
||||
"RunSucceed": "任務執行成功",
|
||||
|
@ -2161,7 +2161,7 @@
|
|||
"riskLevel": "風險等級",
|
||||
"rows": "行",
|
||||
"run": "執行",
|
||||
"runAs": "運行用戶",
|
||||
"runAs": "運行賬號",
|
||||
"runSucceed": "任務執行成功",
|
||||
"runTimes": "執行次數",
|
||||
"running": "運行中",
|
||||
|
|
|
@ -222,5 +222,6 @@
|
|||
"start time": "start time",
|
||||
"success": "success",
|
||||
"system user": "system user",
|
||||
"user": "user"
|
||||
"user": "user",
|
||||
"Command": "Command"
|
||||
}
|
|
@ -220,5 +220,6 @@
|
|||
"start time": "开始时间",
|
||||
"success": "成功",
|
||||
"system user": "系统用户",
|
||||
"user": "用户"
|
||||
"user": "用户",
|
||||
"Command": "命令"
|
||||
}
|
|
@ -78,7 +78,7 @@ class AdHocRunner:
|
|||
|
||||
|
||||
class PlaybookRunner:
|
||||
def __init__(self, inventory, playbook, project_dir='/tmp/', callback=None):
|
||||
def __init__(self, inventory, playbook, project_dir='/tmp/', callback=None, extra_vars=None, ):
|
||||
|
||||
self.id = uuid.uuid4()
|
||||
self.inventory = inventory
|
||||
|
@ -89,6 +89,9 @@ class PlaybookRunner:
|
|||
self.cb = callback
|
||||
self.isolate = True
|
||||
self.envs = {}
|
||||
if extra_vars is None:
|
||||
extra_vars = {}
|
||||
self.extra_vars = extra_vars
|
||||
|
||||
def copy_playbook(self):
|
||||
entry = os.path.basename(self.playbook)
|
||||
|
@ -119,6 +122,7 @@ class PlaybookRunner:
|
|||
status_handler=self.cb.status_handler,
|
||||
host_cwd=self.project_dir,
|
||||
envvars=self.envs,
|
||||
extravars=self.extra_vars,
|
||||
**kwargs
|
||||
)
|
||||
return self.cb
|
||||
|
|
|
@ -4,3 +4,4 @@ from .adhoc import *
|
|||
from .celery import *
|
||||
from .job import *
|
||||
from .playbook import *
|
||||
from .variable import *
|
||||
|
|
|
@ -22,6 +22,7 @@ from ops.models import Job, JobExecution
|
|||
from ops.serializers.job import (
|
||||
JobSerializer, JobExecutionSerializer, FileSerializer, JobTaskStopSerializer
|
||||
)
|
||||
from ops.utils import merge_nodes_and_assets
|
||||
|
||||
__all__ = [
|
||||
'JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobExecutionTaskDetail', 'UsernameHintsAPI'
|
||||
|
@ -36,8 +37,6 @@ from accounts.models import Account
|
|||
from assets.const import Protocol
|
||||
from perms.const import ActionChoices
|
||||
from perms.utils.asset_perm import PermAssetDetailUtil
|
||||
from perms.models import PermNode
|
||||
from perms.utils import UserPermAssetUtil
|
||||
from jumpserver.settings import get_file_md5
|
||||
|
||||
|
||||
|
@ -47,26 +46,12 @@ def set_task_to_serializer_data(serializer, task_id):
|
|||
setattr(serializer, "_data", data)
|
||||
|
||||
|
||||
def merge_nodes_and_assets(nodes, assets, user):
|
||||
if not nodes:
|
||||
return assets
|
||||
perm_util = UserPermAssetUtil(user=user)
|
||||
for node_id in nodes:
|
||||
if node_id == PermNode.FAVORITE_NODE_KEY:
|
||||
node_assets = perm_util.get_favorite_assets()
|
||||
elif node_id == PermNode.UNGROUPED_NODE_KEY:
|
||||
node_assets = perm_util.get_ungroup_assets()
|
||||
else:
|
||||
_, node_assets = perm_util.get_node_all_assets(node_id)
|
||||
assets.extend(node_assets.exclude(id__in=[asset.id for asset in assets]))
|
||||
return assets
|
||||
|
||||
|
||||
class JobViewSet(OrgBulkModelViewSet):
|
||||
serializer_class = JobSerializer
|
||||
filterset_fields = ('name', 'type')
|
||||
search_fields = ('name', 'comment')
|
||||
model = Job
|
||||
_parameters = None
|
||||
|
||||
def check_permissions(self, request):
|
||||
# job: upload_file
|
||||
|
@ -106,10 +91,10 @@ class JobViewSet(OrgBulkModelViewSet):
|
|||
|
||||
def perform_create(self, serializer):
|
||||
run_after_save = serializer.validated_data.pop('run_after_save', False)
|
||||
node_ids = serializer.validated_data.pop('nodes', [])
|
||||
assets = serializer.validated_data.get('assets')
|
||||
assets = merge_nodes_and_assets(node_ids, assets, self.request.user)
|
||||
serializer.validated_data['assets'] = assets
|
||||
self._parameters = serializer.validated_data.pop('parameters', None)
|
||||
nodes = serializer.validated_data.pop('nodes', [])
|
||||
assets = serializer.validated_data.get('assets', [])
|
||||
assets = merge_nodes_and_assets(nodes, assets, self.request.user)
|
||||
if serializer.validated_data.get('type') == Types.upload_file:
|
||||
account_name = serializer.validated_data.get('runas')
|
||||
self.check_upload_permission(assets, account_name)
|
||||
|
@ -126,6 +111,8 @@ class JobViewSet(OrgBulkModelViewSet):
|
|||
|
||||
def run_job(self, job, serializer):
|
||||
execution = job.create_execution()
|
||||
if self._parameters:
|
||||
execution.parameters = JobExecutionSerializer.validate_parameters(self._parameters)
|
||||
execution.creator = self.request.user
|
||||
execution.save()
|
||||
|
||||
|
@ -300,7 +287,7 @@ class UsernameHintsAPI(APIView):
|
|||
permission_classes = [IsValidUser]
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
node_ids = request.data.get('nodes', None)
|
||||
node_ids = request.data.get('nodes', [])
|
||||
asset_ids = request.data.get('assets', [])
|
||||
query = request.data.get('query', None)
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.api.generic import JMSModelViewSet
|
||||
from common.const.http import OPTIONS, GET
|
||||
from common.permissions import IsValidUser
|
||||
from ..models import Variable
|
||||
from ..serializers import VariableSerializer, VariableFormDataSerializer
|
||||
|
||||
__all__ = [
|
||||
'VariableViewSet'
|
||||
]
|
||||
|
||||
|
||||
class VariableViewSet(JMSModelViewSet):
|
||||
queryset = Variable.objects.all()
|
||||
serializer_class = VariableSerializer
|
||||
http_method_names = ['options', 'get']
|
||||
|
||||
@action(methods=[GET], detail=False, serializer_class=VariableFormDataSerializer,
|
||||
permission_classes=[IsValidUser, ], url_path='form_data')
|
||||
def form_data(self, request, *args, **kwargs):
|
||||
# 只是为了动态返回serializer fields info
|
||||
return Response({})
|
|
@ -85,3 +85,8 @@ COMMAND_EXECUTION_DISABLED = _('Command execution disabled')
|
|||
class Scope(models.TextChoices):
|
||||
public = 'public', pgettext_lazy("scope", 'Public')
|
||||
private = 'private', _('Private')
|
||||
|
||||
|
||||
class FieldType(models.TextChoices):
|
||||
text = 'text', _('Text')
|
||||
select = 'select', _('Select')
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 4.1.13 on 2024-10-21 08:02
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0006_database_pg_ssl_mode'),
|
||||
('ops', '0003_alter_adhoc_unique_together_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, to='assets.node', verbose_name='Node'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='assets',
|
||||
field=models.ManyToManyField(blank=True, to='assets.asset', verbose_name='Assets'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='job',
|
||||
unique_together={('name', 'org_id', 'creator', 'type')},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,53 @@
|
|||
# Generated by Django 4.1.13 on 2024-10-30 09:38
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('ops', '0004_job_nodes_alter_job_assets'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='historicaljob',
|
||||
name='periodic_variable',
|
||||
field=models.JSONField(default=dict, verbose_name='Periodic variable'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='job',
|
||||
name='periodic_variable',
|
||||
field=models.JSONField(default=dict, verbose_name='Periodic variable'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Variable',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=1024, null=True, verbose_name='Name')),
|
||||
('var_name', models.CharField(help_text="The variable name used in the script has a fixed prefix 'jms_' followed by the input variable name. For example, if the variable name is 'name,' the final generated environment variable will be 'jms_name'.", max_length=1024, null=True, verbose_name='Variable name')),
|
||||
('default_value', models.CharField(max_length=2048, null=True, verbose_name='Default Value')),
|
||||
('type', models.CharField(default='text', max_length=64, verbose_name='Variable type')),
|
||||
('tips', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Tips')),
|
||||
('required', models.BooleanField(default=False, verbose_name='Required')),
|
||||
('extra_args', models.JSONField(default=dict, verbose_name='ExtraVars')),
|
||||
('adhoc', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='variable', to='ops.adhoc', verbose_name='Adhoc')),
|
||||
('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')),
|
||||
('job', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='variable', to='ops.job', verbose_name='Job')),
|
||||
('playbook', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='variable', to='ops.playbook', verbose_name='Playbook')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Variable',
|
||||
'ordering': ['date_created'],
|
||||
},
|
||||
),
|
||||
]
|
|
@ -5,3 +5,4 @@ from .adhoc import *
|
|||
from .celery import *
|
||||
from .playbook import *
|
||||
from .job import *
|
||||
from .variable import *
|
||||
|
|
|
@ -29,6 +29,7 @@ from ops.ansible.exception import CommandInBlackListException
|
|||
from ops.mixin import PeriodTaskModelMixin
|
||||
from ops.variables import *
|
||||
from ops.const import Types, RunasPolicies, JobStatus, JobModules
|
||||
from ops.utils import merge_nodes_and_assets
|
||||
from orgs.mixins.models import JMSOrgBaseModel
|
||||
from perms.models import AssetPermission
|
||||
from perms.utils import UserPermAssetUtil
|
||||
|
@ -50,11 +51,13 @@ def get_parent_keys(key, include_self=True):
|
|||
class JMSPermedInventory(JMSInventory):
|
||||
def __init__(self,
|
||||
assets,
|
||||
nodes,
|
||||
account_policy='privileged_first',
|
||||
account_prefer='root,Administrator',
|
||||
module=None,
|
||||
host_callback=None,
|
||||
user=None):
|
||||
assets = merge_nodes_and_assets(list(nodes), list(assets), user)
|
||||
super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost=True)
|
||||
self.user = user
|
||||
self.module = module
|
||||
|
@ -149,9 +152,11 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||
playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL)
|
||||
type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Type"))
|
||||
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
||||
assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets"))
|
||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Node"))
|
||||
use_parameter_define = models.BooleanField(default=False, verbose_name=(_('Use Parameter Define')))
|
||||
parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define'))
|
||||
periodic_variable = models.JSONField(default=dict, verbose_name=_('Periodic variable'))
|
||||
runas = models.CharField(max_length=128, default='root', verbose_name=_('Run as'))
|
||||
runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip,
|
||||
verbose_name=_('Run as policy'))
|
||||
|
@ -203,7 +208,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||
|
||||
@property
|
||||
def inventory(self):
|
||||
return JMSPermedInventory(self.assets.all(),
|
||||
return JMSPermedInventory(self.assets.all(), self.nodes.all(),
|
||||
self.runas_policy, self.runas,
|
||||
user=self.creator, module=self.module)
|
||||
|
||||
|
@ -220,7 +225,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
|
|||
|
||||
class Meta:
|
||||
verbose_name = _("Job")
|
||||
unique_together = [('name', 'org_id', 'creator')]
|
||||
unique_together = [('name', 'org_id', 'creator', 'type')]
|
||||
ordering = ['date_created']
|
||||
|
||||
|
||||
|
@ -328,7 +333,7 @@ class JobExecution(JMSOrgBaseModel):
|
|||
if isinstance(self.parameters, str):
|
||||
extra_vars = json.loads(self.parameters)
|
||||
else:
|
||||
extra_vars = {}
|
||||
extra_vars = self.parameters if self.parameters else {}
|
||||
static_variables = self.gather_static_variables()
|
||||
extra_vars.update(static_variables)
|
||||
|
||||
|
@ -349,7 +354,8 @@ class JobExecution(JMSOrgBaseModel):
|
|||
runner = PlaybookRunner(
|
||||
self.inventory_path,
|
||||
self.current_job.playbook.entry,
|
||||
self.private_dir
|
||||
self.private_dir,
|
||||
extra_vars=extra_vars,
|
||||
)
|
||||
elif self.current_job.type == Types.upload_file:
|
||||
job_id = self.current_job.id
|
||||
|
|
|
@ -23,8 +23,6 @@ dangerous_keywords = (
|
|||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class Playbook(JMSBaseModel):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'), null=True)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.db.models import JMSBaseModel
|
||||
from ops.const import FieldType
|
||||
|
||||
|
||||
class Variable(JMSBaseModel):
|
||||
name = models.CharField(max_length=1024, verbose_name=_('Name'), null=True)
|
||||
var_name = models.CharField(
|
||||
max_length=1024, null=True, verbose_name=_('Variable name'),
|
||||
help_text=_("The variable name used in the script has a fixed prefix 'jms_' followed by the input variable "
|
||||
"name. For example, if the variable name is 'name,' the final generated environment variable will "
|
||||
"be 'jms_name'.")
|
||||
)
|
||||
default_value = models.CharField(max_length=2048, verbose_name=_('Default Value'), null=True)
|
||||
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
|
||||
type = models.CharField(max_length=64, default=FieldType.text, verbose_name=_('Variable type'))
|
||||
tips = models.CharField(max_length=1024, default='', verbose_name=_('Tips'), null=True, blank=True)
|
||||
required = models.BooleanField(default=False, verbose_name=_('Required'))
|
||||
extra_args = models.JSONField(default=dict, verbose_name=_('ExtraVars'))
|
||||
playbook = models.ForeignKey(
|
||||
'ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.CASCADE, related_name='variable'
|
||||
)
|
||||
adhoc = models.ForeignKey(
|
||||
'ops.AdHoc', verbose_name=_("Adhoc"), null=True, on_delete=models.CASCADE, related_name='variable'
|
||||
)
|
||||
job = models.ForeignKey('ops.Job', verbose_name=_("Job"), null=True, on_delete=models.CASCADE,
|
||||
related_name='variable')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def form_data(self):
|
||||
return {
|
||||
'var_name': self.var_name,
|
||||
'label': self.name,
|
||||
'help_text': self.tips,
|
||||
'read_only': False,
|
||||
'required': self.required,
|
||||
'type': self.type,
|
||||
'write_only': False,
|
||||
'default': self.default_value,
|
||||
'extra_args': self.extra_args,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Variable")
|
||||
ordering = ['date_created']
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .celery import *
|
||||
from .variable import *
|
||||
from .adhoc import *
|
||||
|
|
|
@ -3,17 +3,20 @@ from __future__ import unicode_literals
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers.fields import ReadableHiddenField, LabeledChoiceField
|
||||
from common.serializers import WritableNestedModelSerializer
|
||||
from common.serializers.fields import ReadableHiddenField
|
||||
from common.serializers.mixin import CommonBulkModelSerializer
|
||||
from .mixin import ScopeSerializerMixin
|
||||
from ..const import Scope
|
||||
from ..models import AdHoc
|
||||
from ops.serializers import AdhocVariableSerializer
|
||||
|
||||
|
||||
class AdHocSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
|
||||
class AdHocSerializer(ScopeSerializerMixin, CommonBulkModelSerializer, WritableNestedModelSerializer):
|
||||
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
|
||||
variable = AdhocVariableSerializer(many=True, required=False, allow_null=True, label=_('Variable'))
|
||||
|
||||
class Meta:
|
||||
model = AdHoc
|
||||
read_only_field = ["id", "creator", "date_created", "date_updated", "created_by"]
|
||||
fields = read_only_field + ["id", "name", "scope", "module", "args", "comment"]
|
||||
fields_m2m = ['variable']
|
||||
fields = read_only_field + fields_m2m + ["id", "name", "scope", "module", "args", "comment"]
|
||||
|
|
|
@ -3,20 +3,25 @@ import uuid
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models import Asset
|
||||
from common.serializers.fields import ReadableHiddenField
|
||||
from assets.models import Asset, Node
|
||||
from common.serializers import WritableNestedModelSerializer
|
||||
from common.serializers.fields import ReadableHiddenField, ObjectRelatedField
|
||||
from ops.mixin import PeriodTaskSerializerMixin
|
||||
from ops.models import Job, JobExecution
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ops.serializers import JobVariableSerializer
|
||||
|
||||
|
||||
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
|
||||
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin, WritableNestedModelSerializer):
|
||||
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
|
||||
run_after_save = serializers.BooleanField(label=_("Execute after saving"), default=False, required=False)
|
||||
nodes = serializers.ListField(required=False, child=serializers.CharField())
|
||||
date_last_run = serializers.DateTimeField(label=_('Date last run'), read_only=True)
|
||||
name = serializers.CharField(label=_('Name'), max_length=128, allow_blank=True, required=False)
|
||||
assets = serializers.PrimaryKeyRelatedField(label=_('Assets'), queryset=Asset.objects, many=True, required=False)
|
||||
nodes = ObjectRelatedField(label=_('Nodes'), queryset=Node.objects, many=True, required=False)
|
||||
variable = JobVariableSerializer(many=True, required=False, allow_null=True, label=_('Variable'))
|
||||
parameters = serializers.JSONField(label=_('Parameters'), default={}, write_only=True, required=False,
|
||||
allow_null=True)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
instant = data.get('instant', False)
|
||||
|
@ -39,6 +44,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
|
|||
"id", "date_last_run", "date_created",
|
||||
"date_updated", "average_time_cost"
|
||||
]
|
||||
fields_m2m = ['variable']
|
||||
fields = read_only_fields + [
|
||||
"name", "instant", "type", "module",
|
||||
"args", "playbook", "assets",
|
||||
|
@ -46,8 +52,8 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
|
|||
"use_parameter_define", "parameters_define",
|
||||
"timeout", "chdir", "comment", "summary",
|
||||
"is_periodic", "interval", "crontab", "nodes",
|
||||
"run_after_save"
|
||||
]
|
||||
"run_after_save", "parameters", "periodic_variable"
|
||||
] + fields_m2m
|
||||
extra_kwargs = {
|
||||
'average_time_cost': {'label': _('Duration')},
|
||||
}
|
||||
|
@ -97,3 +103,13 @@ class JobExecutionSerializer(BulkOrgResourceModelSerializer):
|
|||
if job_obj.creator != self.context['request'].user:
|
||||
raise serializers.ValidationError(_("You do not have permission for the current job."))
|
||||
return job_obj
|
||||
|
||||
@staticmethod
|
||||
def validate_parameters(parameters):
|
||||
prefix = "jms_"
|
||||
new_parameters = {}
|
||||
for key, value in parameters.items():
|
||||
if not key.startswith("jms_"):
|
||||
key = prefix + key
|
||||
new_parameters[key] = value
|
||||
return new_parameters
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import os
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import WritableNestedModelSerializer
|
||||
from common.serializers.fields import ReadableHiddenField
|
||||
from common.serializers.mixin import CommonBulkModelSerializer
|
||||
from ops.models import Playbook
|
||||
from .mixin import ScopeSerializerMixin
|
||||
from ops.serializers.variable import PlaybookVariableSerializer
|
||||
|
||||
|
||||
def parse_playbook_name(path):
|
||||
|
@ -13,10 +15,11 @@ def parse_playbook_name(path):
|
|||
return file_name.split(".")[-2]
|
||||
|
||||
|
||||
class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
|
||||
class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer, WritableNestedModelSerializer):
|
||||
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
|
||||
path = serializers.FileField(required=False)
|
||||
|
||||
variable = PlaybookVariableSerializer(many=True, required=False, allow_null=True, label=_('Variable'))
|
||||
|
||||
def to_internal_value(self, data):
|
||||
name = data.get('name', False)
|
||||
if not name and data.get('path'):
|
||||
|
@ -26,7 +29,8 @@ class PlaybookSerializer(ScopeSerializerMixin, CommonBulkModelSerializer):
|
|||
class Meta:
|
||||
model = Playbook
|
||||
read_only_fields = ["id", "date_created", "date_updated", "created_by"]
|
||||
fields = read_only_fields + [
|
||||
fields_m2m = ['variable']
|
||||
fields = read_only_fields + fields_m2m + [
|
||||
"id", 'path', 'scope', "name", "comment", "creator",
|
||||
'create_method', 'vcs_url',
|
||||
]
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers.fields import ReadableHiddenField, LabeledChoiceField, EncryptedField
|
||||
from common.serializers.mixin import CommonBulkModelSerializer
|
||||
from ops.const import FieldType
|
||||
from ops.models import Variable, AdHoc, Job, Playbook
|
||||
|
||||
__all__ = [
|
||||
'VariableSerializer', 'AdhocVariableSerializer', 'JobVariableSerializer', 'PlaybookVariableSerializer',
|
||||
'VariableFormDataSerializer'
|
||||
]
|
||||
|
||||
|
||||
class VariableSerializer(CommonBulkModelSerializer):
|
||||
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
|
||||
type = LabeledChoiceField(
|
||||
choices=FieldType.choices, default=FieldType.text, label=_("Variable Type")
|
||||
)
|
||||
extra_args = serializers.CharField(
|
||||
max_length=1024, label=_("ExtraVars"), required=False, allow_blank=True,
|
||||
help_text=_(
|
||||
"Each item is on a separate line, with each line separated by a colon. The part before the colon is the "
|
||||
"display content, and the part after the colon is the value.")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Variable
|
||||
read_only_fields = ["id", "date_created", "date_updated", "created_by", "creator"]
|
||||
fields = read_only_fields + [
|
||||
"name", "var_name", "type", 'required', 'default_value', 'tips', 'adhoc', 'playbook', 'job', 'form_data',
|
||||
'extra_args'
|
||||
]
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
type = attrs.get('type')
|
||||
attrs['extra_args'] = {}
|
||||
if type == FieldType.text:
|
||||
attrs['default_value'] = self.initial_data.get('text_default_value')
|
||||
elif type == FieldType.select:
|
||||
attrs['default_value'] = self.initial_data.get('select_default_value')
|
||||
options = self.initial_data.get('extra_args', '')
|
||||
attrs['extra_args'] = {"options": options}
|
||||
return attrs
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
if instance.type == FieldType.select:
|
||||
data['extra_args'] = instance.extra_args.get('options', '')
|
||||
data['select_default_value'] = instance.default_value
|
||||
if instance.type == FieldType.text:
|
||||
data['text_default_value'] = instance.default_value
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
queryset = queryset.prefetch_related('adhoc', 'job', 'playbook')
|
||||
return queryset
|
||||
|
||||
|
||||
class AdhocVariableSerializer(VariableSerializer):
|
||||
adhoc = serializers.PrimaryKeyRelatedField(queryset=AdHoc.objects, required=False)
|
||||
|
||||
class Meta(VariableSerializer.Meta):
|
||||
fields = VariableSerializer.Meta.fields
|
||||
|
||||
|
||||
class JobVariableSerializer(VariableSerializer):
|
||||
job = serializers.PrimaryKeyRelatedField(queryset=Job.objects, required=False)
|
||||
|
||||
class Meta(VariableSerializer.Meta):
|
||||
fields = VariableSerializer.Meta.fields
|
||||
|
||||
|
||||
class PlaybookVariableSerializer(VariableSerializer):
|
||||
playbook = serializers.PrimaryKeyRelatedField(queryset=Playbook.objects, required=False)
|
||||
|
||||
class Meta(VariableSerializer.Meta):
|
||||
fields = VariableSerializer.Meta.fields
|
||||
|
||||
|
||||
def create_dynamic_text_choices(options):
|
||||
"""
|
||||
动态创建一个 TextChoices 子类。`options` 应该是一个列表,
|
||||
格式为 [(value1, display1), (value2, display2), ...]
|
||||
"""
|
||||
attrs = {
|
||||
key.upper(): value for value, key in options
|
||||
}
|
||||
attrs['choices'] = options
|
||||
return type('DynamicTextChoices', (models.TextChoices,), attrs)
|
||||
|
||||
|
||||
class VariableFormDataSerializer(serializers.Serializer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
request = self.context.get('request')
|
||||
if not request:
|
||||
return
|
||||
params = request.query_params
|
||||
job = params.get('job')
|
||||
adhoc = params.get('adhoc')
|
||||
playbook = params.get('playbook')
|
||||
if job:
|
||||
variables = Variable.objects.filter(job=job).all()
|
||||
elif adhoc:
|
||||
variables = Variable.objects.filter(adhoc=adhoc).all()
|
||||
else:
|
||||
variables = Variable.objects.filter(playbook=playbook).all()
|
||||
dynamic_fields = [var.form_data for var in variables]
|
||||
|
||||
if dynamic_fields:
|
||||
for field in dynamic_fields:
|
||||
field_type = field['type']
|
||||
required = field['required']
|
||||
var_name = field["var_name"]
|
||||
label = field["label"]
|
||||
help_text = field['help_text']
|
||||
default = field['default']
|
||||
if field_type == FieldType.text:
|
||||
self.fields[var_name] = serializers.CharField(
|
||||
max_length=1024, label=label, help_text=help_text, required=required
|
||||
)
|
||||
elif field_type == FieldType.select:
|
||||
extra_args = field.get('extra_args', {})
|
||||
options = extra_args.get('options', '').splitlines()
|
||||
|
||||
DynamicFieldType = models.TextChoices(
|
||||
'DynamicFieldType',
|
||||
{
|
||||
option.split(':')[0]: option.split(':')[1] for option in
|
||||
options
|
||||
}
|
||||
)
|
||||
self.fields[var_name] = LabeledChoiceField(
|
||||
choices=DynamicFieldType.choices, required=required, label=label,
|
||||
help_text=help_text
|
||||
)
|
||||
if required and default is not None:
|
||||
self.fields[var_name].default = default
|
|
@ -10,6 +10,7 @@ from django_celery_beat.models import PeriodicTask
|
|||
from common.const.crontab import CRONTAB_AT_AM_TWO
|
||||
from common.utils import get_logger, get_object_or_none, get_log_keep_day
|
||||
from ops.celery import app
|
||||
from ops.serializers.job import JobExecutionSerializer
|
||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
||||
from .celery.decorator import (
|
||||
register_as_period_task, after_app_ready_start
|
||||
|
@ -64,6 +65,8 @@ def run_ops_job(job_id):
|
|||
with tmp_to_org(job.org):
|
||||
execution = job.create_execution()
|
||||
execution.creator = job.creator
|
||||
if job.periodic_variable:
|
||||
execution.parameters = JobExecutionSerializer.validate_parameters(job.periodic_variable)
|
||||
_run_ops_job_execution(execution)
|
||||
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ bulk_router = BulkRouter()
|
|||
bulk_router.register(r'adhocs', api.AdHocViewSet, 'adhoc')
|
||||
bulk_router.register(r'playbooks', api.PlaybookViewSet, 'playbook')
|
||||
bulk_router.register(r'jobs', api.JobViewSet, 'job')
|
||||
bulk_router.register(r'variable', api.VariableViewSet, 'variable')
|
||||
bulk_router.register(r'job-executions', api.JobExecutionViewSet, 'job-execution')
|
||||
|
||||
router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task')
|
||||
|
||||
router.register(r'tasks', api.CeleryTaskViewSet, 'task')
|
||||
|
|
|
@ -5,6 +5,9 @@ from django.conf import settings
|
|||
|
||||
from common.utils import get_logger, make_dirs
|
||||
from jumpserver.const import PROJECT_DIR
|
||||
from perms.models import PermNode
|
||||
from perms.utils import UserPermAssetUtil
|
||||
from assets.models import Asset, Node
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -29,3 +32,19 @@ def get_ansible_log_verbosity(verbosity=0):
|
|||
return 1
|
||||
return verbosity
|
||||
|
||||
|
||||
def merge_nodes_and_assets(nodes, assets, user):
|
||||
if not nodes:
|
||||
return assets
|
||||
perm_util = UserPermAssetUtil(user=user)
|
||||
for node_id in nodes:
|
||||
if isinstance(node_id, Node):
|
||||
node_id = node_id.id
|
||||
if node_id == PermNode.FAVORITE_NODE_KEY:
|
||||
node_assets = perm_util.get_favorite_assets()
|
||||
elif node_id == PermNode.UNGROUPED_NODE_KEY:
|
||||
node_assets = perm_util.get_ungroup_assets()
|
||||
else:
|
||||
_, node_assets = perm_util.get_node_all_assets(node_id)
|
||||
assets.extend(node_assets.exclude(id__in=[asset.id for asset in assets]))
|
||||
return assets
|
||||
|
|
|
@ -51,7 +51,7 @@ class NodePermedSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'name', 'key', 'value',
|
||||
'id', 'name', 'key', 'value', 'full_value',
|
||||
'org_id', "assets_amount"
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
|
Loading…
Reference in New Issue