diff --git a/app/admin/templates/job_edit.html b/app/admin/templates/job_edit.html new file mode 100644 index 0000000..181e2ab --- /dev/null +++ b/app/admin/templates/job_edit.html @@ -0,0 +1,33 @@ +{% extends "sqladmin/layout.html" %} +{% from 'sqladmin/_macros.html' import render_form_fields %} +{% block content %} + +### 编辑{{ model_view.name }} + +{% if error %} + +{{ error }} + +{% endif %} + +
+ {{ render_form_fields(form, form_opts=form_opts) }} + +
+ +
+ +
+ 出于安全考虑,编辑页不回显历史密文。留空表示不修改;填写 JSON 对象将覆盖原值并重新加密保存。 +
+
+
+ +
+ + 取消 +
+
+ +{% endblock %} + diff --git a/app/admin/views.py b/app/admin/views.py index 87f15b5..081936a 100644 --- a/app/admin/views.py +++ b/app/admin/views.py @@ -69,9 +69,15 @@ class JobAdmin(ModelView, model=Job): # 为 Job 详情页指定模板(用于调整按钮间距) details_template = "job_details.html" + # 编辑页:secret_cfg 只写不读(不回显密文;留空表示不更新) + edit_template = "job_edit.html" + # 列表页模板:加入每行 Run Now list_template = "job_list.html" + # 编辑页排除 secret_cfg,避免回显密文;由自定义模板额外渲染一个空输入框 + form_edit_rules = [Job.id, Job.enabled, Job.cron_expr, Job.handler_path, Job.public_cfg] + column_labels = { "id": "任务ID", "enabled": "启用", @@ -141,16 +147,38 @@ class JobAdmin(ModelView, model=Job): raise ValueError("public_cfg must be a JSON object") data["public_cfg"] = pcfg - # secret_cfg:必须是合法 JSON 对象(dict),并且保存时必须加密落库 - scfg = data.get("secret_cfg") - if isinstance(scfg, str): + # secret_cfg: + # - 创建:必须是合法 JSON 对象(dict),并且保存时必须加密落库 + # - 编辑:出于安全考虑不回显密文;若留空则保留原密文不更新;若填写则按 JSON 校验并加密覆盖 + if is_created: + scfg = data.get("secret_cfg") + if isinstance(scfg, str): + try: + scfg = json.loads(scfg) + except json.JSONDecodeError as e: + raise ValueError("secret_cfg must be a JSON object") from e + if not isinstance(scfg, dict): + raise ValueError("secret_cfg must be a JSON object") + data["secret_cfg"] = encrypt_json(scfg) + else: + # 自定义编辑页会以 textarea 传回 secret_cfg(可能不存在或为空) try: - scfg = json.loads(scfg) - except json.JSONDecodeError as e: - raise ValueError("secret_cfg must be a JSON object") from e - if not isinstance(scfg, dict): - raise ValueError("secret_cfg must be a JSON object") - data["secret_cfg"] = encrypt_json(scfg) + form = await request.form() + raw = form.get("secret_cfg") + except Exception: + raw = None + raw_s = str(raw).strip() if raw is not None else "" + if not raw_s: + # 留空:不更新密文字段 + data.pop("secret_cfg", None) + else: + try: + scfg2 = json.loads(raw_s) + except json.JSONDecodeError as e: + raise ValueError("secret_cfg must be a JSON object") from e + if not isinstance(scfg2, dict): + raise ValueError("secret_cfg must be a JSON object") + data["secret_cfg"] = encrypt_json(scfg2) class JobLogAdmin(ModelView, model=JobLog): @@ -208,6 +236,11 @@ class JobLogAdmin(ModelView, model=JobLog): column_formatters_detail = { JobLog.started_at: lambda m, a: _fmt_dt_seconds(m.started_at), JobLog.finished_at: lambda m, a: _fmt_dt_seconds(m.finished_at), + JobLog.message: lambda m, a: Markup( + "
"
+            + (m.message or "")
+            + "
" + ), JobLog.traceback: lambda m, a: Markup(f"
{m.traceback or ''}
"), JobLog.run_log: lambda m, a: Markup( "
"