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
fit2bot 2024-11-07 10:38:34 +08:00 committed by GitHub
parent 8f11167db0
commit c96ae1022b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 565 additions and 141 deletions

View File

@ -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):

View File

@ -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

View File

@ -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 "已经绑定"

View File

@ -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

View File

@ -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"
}

View File

@ -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": "タスクが成功",

View File

@ -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": "设置参数"
}

View File

@ -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": "運行中",

View File

@ -222,5 +222,6 @@
"start time": "start time",
"success": "success",
"system user": "system user",
"user": "user"
"user": "user",
"Command": "Command"
}

View File

@ -220,5 +220,6 @@
"start time": "开始时间",
"success": "成功",
"system user": "系统用户",
"user": "用户"
"user": "用户",
"Command": "命令"
}

View File

@ -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

View File

@ -4,3 +4,4 @@ from .adhoc import *
from .celery import *
from .job import *
from .playbook import *
from .variable import *

View File

@ -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)

25
apps/ops/api/variable.py Normal file
View File

@ -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({})

View File

@ -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')

View File

@ -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')},
),
]

View File

@ -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'],
},
),
]

View File

@ -5,3 +5,4 @@ from .adhoc import *
from .celery import *
from .playbook import *
from .job import *
from .variable import *

View File

@ -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

View File

@ -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)

View File

@ -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']

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
#
from .celery import *
from .variable import *
from .adhoc import *

View File

@ -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"]

View File

@ -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

View File

@ -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',
]

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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