# -*- coding: utf-8 -*- from datetime import datetime from flask import Blueprint, current_app, jsonify, render_template, request from .auth import login_required plugin_schedules_bp = Blueprint("plugin_schedules", __name__, url_prefix="/plugin_schedules") def _normalize_datetime_text(value): """统一时间文本格式为 `YYYY-MM-DD HH:MM:SS`。 兼容场景: 1. 数据库返回 datetime 对象; 2. 已经是 ISO 字符串(包含 T); 3. 兜底保留原值字符串,避免因为解析失败把数据抹掉。 """ if value is None: return value if isinstance(value, datetime): return value.strftime("%Y-%m-%d %H:%M:%S") text = str(value) # 先处理常见 ISO 格式,直接去掉 T 并截到秒,避免前端显示异常。 if "T" in text: return text.replace("T", " ")[:19] return text @plugin_schedules_bp.route("/") @login_required def page_plugin_schedules(): return render_template("plugin_schedules.html") @plugin_schedules_bp.route("/api/schedules", methods=["GET"]) @login_required def api_list_schedules(): server = current_app.dashboard_server data = server.plugin_schedule_manager.list_schedules_with_runtime() # 后端统一格式化时间字段,避免前端出现 Fri, 17 Apr 2026 ... 这类 RFC 时间串。 for row in data: for key in ("next_run_at", "last_run_at", "created_at", "updated_at"): if key in row: row[key] = _normalize_datetime_text(row.get(key)) return jsonify({"success": True, "data": data}) @plugin_schedules_bp.route("/api/actions", methods=["GET"]) @login_required def api_list_actions(): server = current_app.dashboard_server data = server.plugin_schedule_manager.get_available_plugin_actions() return jsonify({"success": True, "data": data}) @plugin_schedules_bp.route("/api/schedules/", methods=["PUT"]) @login_required def api_update_schedule(schedule_id: int): server = current_app.dashboard_server payload = request.get_json(silent=True) or {} updates = {} for key in ( "action_name", "description", "trigger_type", "trigger_config", "target_scope", "target_config", "payload", "enabled", ): if key in payload: updates[key] = payload[key] if not updates: return jsonify({"success": False, "message": "没有可更新字段"}), 400 ok = server.plugin_schedule_manager.update_schedule(schedule_id, updates) if not ok: return jsonify({"success": False, "message": "更新失败"}), 500 return jsonify({"success": True, "message": "更新成功"}) @plugin_schedules_bp.route("/api/schedules//trigger", methods=["POST"]) @login_required def api_trigger_schedule(schedule_id: int): server = current_app.dashboard_server ok, msg = server.plugin_schedule_manager.trigger_now(schedule_id) code = 200 if ok else 400 return jsonify({"success": ok, "message": msg}), code @plugin_schedules_bp.route("/api/schedules//logs", methods=["GET"]) @login_required def api_schedule_logs(schedule_id: int): server = current_app.dashboard_server limit = int(request.args.get("limit", 100)) logs = server.plugin_schedule_manager.get_logs(schedule_id, limit=limit) # 日志时间统一成固定格式,避免被 Flask JSON 序列化成 RFC 字符串。 for row in logs: if "triggered_at" in row: row["triggered_at"] = _normalize_datetime_text(row.get("triggered_at")) return jsonify({"success": True, "data": logs}) @plugin_schedules_bp.route("/api/reload", methods=["POST"]) @login_required def api_reload_schedules(): server = current_app.dashboard_server server.plugin_schedule_manager.reload_from_db() return jsonify({"success": True, "message": "已按数据库配置重载插件调度"})