137 lines
5.0 KiB
Python
137 lines
5.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
from datetime import datetime
|
|
from flask import Blueprint, current_app, jsonify, render_template, request
|
|
|
|
from utils.decorator.async_job import async_job
|
|
from .auth import login_required
|
|
|
|
|
|
system_jobs_bp = Blueprint("system_jobs", __name__, url_prefix="/system_jobs")
|
|
|
|
|
|
def _normalize_datetime_text(value):
|
|
"""统一时间文本格式为 `YYYY-MM-DD HH:MM:SS`。"""
|
|
if value is None:
|
|
return value
|
|
if isinstance(value, datetime):
|
|
return value.strftime("%Y-%m-%d %H:%M:%S")
|
|
text = str(value)
|
|
if "T" in text:
|
|
return text.replace("T", " ")[:19]
|
|
return text
|
|
|
|
|
|
@system_jobs_bp.route("/")
|
|
@login_required
|
|
def page_system_jobs():
|
|
return render_template("system_jobs.html")
|
|
|
|
|
|
@system_jobs_bp.route("/api/jobs", methods=["GET"])
|
|
@login_required
|
|
def api_list_jobs():
|
|
server = current_app.dashboard_server
|
|
db_rows = server.system_job_db.list_jobs()
|
|
runtime_rows = async_job.get_jobs_snapshot()
|
|
runtime_by_key = {row.get("job_key", ""): row for row in runtime_rows if row.get("job_key")}
|
|
|
|
result = []
|
|
for row in db_rows:
|
|
job_key = row.get("job_key")
|
|
runtime = runtime_by_key.get(job_key, {})
|
|
result.append(
|
|
{
|
|
"job_key": job_key,
|
|
"name": row.get("name", ""),
|
|
"description": row.get("description", ""),
|
|
"trigger_type": row.get("trigger_type", ""),
|
|
"trigger_config": row.get("trigger_config", {}),
|
|
"enabled": bool(row.get("enabled", 0)),
|
|
"runtime_job_id": runtime.get("id"),
|
|
"runtime_enabled": runtime.get("enabled"),
|
|
"running": runtime.get("running", False),
|
|
"trigger_text": runtime.get("trigger_text", ""),
|
|
"last_run_at": _normalize_datetime_text(runtime.get("last_run_at")),
|
|
"last_status": runtime.get("last_status"),
|
|
"last_error": runtime.get("last_error"),
|
|
"last_duration_ms": runtime.get("last_duration_ms"),
|
|
"next_run_at": _normalize_datetime_text(runtime.get("next_run_at")),
|
|
"run_count": runtime.get("run_count", 0),
|
|
"success_count": runtime.get("success_count", 0),
|
|
"fail_count": runtime.get("fail_count", 0),
|
|
}
|
|
)
|
|
|
|
return jsonify({"success": True, "data": result})
|
|
|
|
|
|
@system_jobs_bp.route("/api/jobs/<job_key>", methods=["PUT"])
|
|
@login_required
|
|
def api_update_job(job_key: str):
|
|
server = current_app.dashboard_server
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
updates = {}
|
|
for key in ("name", "description", "trigger_type", "trigger_config", "enabled"):
|
|
if key in payload:
|
|
updates[key] = payload[key]
|
|
|
|
if not updates:
|
|
return jsonify({"success": False, "message": "没有可更新字段"}), 400
|
|
|
|
ok = server.system_job_db.update_job(job_key, updates)
|
|
if not ok:
|
|
return jsonify({"success": False, "message": "数据库更新失败"}), 500
|
|
|
|
# 配置变更后立即重载调度器,确保实时生效
|
|
server.system_job_loader.reload_from_db()
|
|
return jsonify({"success": True, "message": "更新成功"})
|
|
|
|
|
|
@system_jobs_bp.route("/api/jobs/<job_key>/trigger", methods=["POST"])
|
|
@login_required
|
|
def api_trigger_job(job_key: str):
|
|
server = current_app.dashboard_server
|
|
job_id = async_job.get_job_id_by_key(job_key)
|
|
if not job_id:
|
|
server.system_job_loader.reload_from_db()
|
|
job_id = async_job.get_job_id_by_key(job_key)
|
|
if not job_id:
|
|
return jsonify({"success": False, "message": "任务未加载或已禁用"}), 404
|
|
|
|
ok, msg = async_job.trigger_job_now(job_id, operator="dashboard")
|
|
code = 200 if ok else 400
|
|
return jsonify({"success": ok, "message": msg}), code
|
|
|
|
|
|
@system_jobs_bp.route("/api/jobs/<job_key>/logs", methods=["GET"])
|
|
@login_required
|
|
def api_job_logs(job_key: str):
|
|
server = current_app.dashboard_server
|
|
limit = int(request.args.get("limit", 100))
|
|
db_logs = server.system_job_db.get_job_logs(job_key, limit=limit)
|
|
# 为了兼容前端既有表头(time/level/message),这里做一层字段映射。
|
|
logs = []
|
|
for row in db_logs:
|
|
status = str(row.get("status") or "")
|
|
level = "error" if status == "failed" else ("success" if status == "success" else "info")
|
|
logs.append(
|
|
{
|
|
"time": _normalize_datetime_text(row.get("triggered_at")),
|
|
"level": level,
|
|
"message": row.get("summary") or "",
|
|
"status": status,
|
|
"duration_ms": row.get("duration_ms"),
|
|
"detail_json": row.get("detail_json") or {},
|
|
}
|
|
)
|
|
return jsonify({"success": True, "data": logs})
|
|
|
|
|
|
@system_jobs_bp.route("/api/reload", methods=["POST"])
|
|
@login_required
|
|
def api_reload_jobs():
|
|
server = current_app.dashboard_server
|
|
server.system_job_loader.reload_from_db()
|
|
return jsonify({"success": True, "message": "已按数据库配置重载系统定时任务"})
|