refactor: 删除版本更新功能 + 报表页自动刷新
删除版本与更新功能: - routes/admin_api/update.py: 删除整个文件 - routes/admin_api/__init__.py: 移除 update 模块注册 - admin-frontend/src/pages/SystemPage.vue: 移除版本更新UI区块 - admin-frontend/src/api/update.js: 删除整个文件 - 删除 static/admin/assets/update-*.js 报表页自动刷新: - admin-frontend/src/pages/ReportPage.vue: 添加 setInterval 每1秒刷新 - 在 onMounted 启动定时器,onUnmounted 清除 - 覆盖统计数据、运行中任务、系统信息等所有动态数据 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,6 @@ admin_api_bp = Blueprint("admin_api", __name__, url_prefix="/yuyx/api")
|
||||
|
||||
# Import side effects: register routes on blueprint
|
||||
from routes.admin_api import core as _core # noqa: F401
|
||||
from routes.admin_api import update as _update # noqa: F401
|
||||
|
||||
# Export security blueprint for app registration
|
||||
from routes.admin_api.security import security_bp # noqa: F401
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from flask import jsonify, request, session
|
||||
|
||||
from routes.admin_api import admin_api_bp
|
||||
from routes.decorators import admin_required
|
||||
from services.time_utils import get_beijing_now
|
||||
from services.update_files import (
|
||||
ensure_update_dirs,
|
||||
get_update_job_log_path,
|
||||
get_update_request_path,
|
||||
get_update_result_path,
|
||||
get_update_status_path,
|
||||
load_json_file,
|
||||
sanitize_job_id,
|
||||
tail_text_file,
|
||||
write_json_atomic,
|
||||
)
|
||||
|
||||
|
||||
def _request_ip() -> str:
|
||||
try:
|
||||
return request.headers.get("X-Forwarded-For", "").split(",")[0].strip() or request.remote_addr or ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _make_job_id(prefix: str = "upd") -> str:
|
||||
now_str = get_beijing_now().strftime("%Y%m%d_%H%M%S")
|
||||
rand = uuid.uuid4().hex[:8]
|
||||
return f"{prefix}_{now_str}_{rand}"
|
||||
|
||||
|
||||
def _has_pending_request() -> bool:
|
||||
try:
|
||||
return os.path.exists(get_update_request_path())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _parse_bool_field(data: dict, key: str) -> bool | None:
|
||||
if not isinstance(data, dict) or key not in data:
|
||||
return None
|
||||
value = data.get(key)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, int):
|
||||
if value in (0, 1):
|
||||
return bool(value)
|
||||
raise ValueError(f"{key} 必须是 0/1 或 true/false")
|
||||
if isinstance(value, str):
|
||||
text = value.strip().lower()
|
||||
if text in ("1", "true", "yes", "y", "on"):
|
||||
return True
|
||||
if text in ("0", "false", "no", "n", "off", ""):
|
||||
return False
|
||||
raise ValueError(f"{key} 必须是 0/1 或 true/false")
|
||||
if value is None:
|
||||
return None
|
||||
raise ValueError(f"{key} 必须是 0/1 或 true/false")
|
||||
|
||||
|
||||
def _admin_reauth_required() -> bool:
|
||||
try:
|
||||
return time.time() > float(session.get("admin_reauth_until", 0) or 0)
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
|
||||
@admin_api_bp.route("/update/status", methods=["GET"])
|
||||
@admin_required
|
||||
def get_update_status_api():
|
||||
"""读取宿主机 Update-Agent 写入的 update/status.json。"""
|
||||
ensure_update_dirs()
|
||||
status_path = get_update_status_path()
|
||||
data, err = load_json_file(status_path)
|
||||
if err:
|
||||
return jsonify({"ok": False, "error": f"读取 status 失败: {err}", "data": data}), 200
|
||||
if not data:
|
||||
return jsonify({"ok": False, "error": "未发现更新状态(Update-Agent 可能未运行)"}), 200
|
||||
data.setdefault("update_available", False)
|
||||
return jsonify({"ok": True, "data": data}), 200
|
||||
|
||||
|
||||
@admin_api_bp.route("/update/result", methods=["GET"])
|
||||
@admin_required
|
||||
def get_update_result_api():
|
||||
"""读取 update/result.json(最近一次更新执行结果)。"""
|
||||
ensure_update_dirs()
|
||||
result_path = get_update_result_path()
|
||||
data, err = load_json_file(result_path)
|
||||
if err:
|
||||
return jsonify({"ok": False, "error": f"读取 result 失败: {err}", "data": data}), 200
|
||||
if not data:
|
||||
return jsonify({"ok": True, "data": None}), 200
|
||||
return jsonify({"ok": True, "data": data}), 200
|
||||
|
||||
|
||||
@admin_api_bp.route("/update/log", methods=["GET"])
|
||||
@admin_required
|
||||
def get_update_log_api():
|
||||
"""读取 update/jobs/<job_id>.log 的末尾内容(用于后台展示进度)。"""
|
||||
ensure_update_dirs()
|
||||
|
||||
job_id = sanitize_job_id(request.args.get("job_id"))
|
||||
if not job_id:
|
||||
# 若未指定,则尝试用 result.json 的 job_id
|
||||
result_data, _ = load_json_file(get_update_result_path())
|
||||
job_id = sanitize_job_id(result_data.get("job_id") if isinstance(result_data, dict) else None)
|
||||
|
||||
if not job_id:
|
||||
return jsonify({"ok": True, "job_id": None, "log": "", "truncated": False}), 200
|
||||
|
||||
max_bytes = request.args.get("max_bytes", "200000")
|
||||
try:
|
||||
max_bytes_i = int(max_bytes)
|
||||
except Exception:
|
||||
max_bytes_i = 200_000
|
||||
max_bytes_i = max(10_000, min(2_000_000, max_bytes_i))
|
||||
|
||||
log_path = get_update_job_log_path(job_id)
|
||||
text, truncated = tail_text_file(log_path, max_bytes=max_bytes_i)
|
||||
return jsonify({"ok": True, "job_id": job_id, "log": text, "truncated": truncated}), 200
|
||||
|
||||
|
||||
@admin_api_bp.route("/update/check", methods=["POST"])
|
||||
@admin_required
|
||||
def request_update_check_api():
|
||||
"""请求宿主机 Update-Agent 立刻执行一次检查更新。"""
|
||||
ensure_update_dirs()
|
||||
if _has_pending_request():
|
||||
return jsonify({"error": "已有更新请求正在处理中,请稍后再试"}), 409
|
||||
|
||||
job_id = _make_job_id(prefix="chk")
|
||||
payload = {
|
||||
"job_id": job_id,
|
||||
"action": "check",
|
||||
"requested_at": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"requested_by": session.get("admin_username") or "",
|
||||
"requested_ip": _request_ip(),
|
||||
}
|
||||
write_json_atomic(get_update_request_path(), payload)
|
||||
return jsonify({"success": True, "job_id": job_id}), 200
|
||||
|
||||
|
||||
@admin_api_bp.route("/update/run", methods=["POST"])
|
||||
@admin_required
|
||||
def request_update_run_api():
|
||||
"""请求宿主机 Update-Agent 执行一键更新并重启服务。"""
|
||||
ensure_update_dirs()
|
||||
if _admin_reauth_required():
|
||||
return jsonify({"error": "需要二次确认", "code": "reauth_required"}), 401
|
||||
if _has_pending_request():
|
||||
return jsonify({"error": "已有更新请求正在处理中,请稍后再试"}), 409
|
||||
|
||||
data = request.json or {}
|
||||
try:
|
||||
build_no_cache = _parse_bool_field(data, "build_no_cache")
|
||||
if build_no_cache is None:
|
||||
build_no_cache = _parse_bool_field(data, "no_cache")
|
||||
build_pull = _parse_bool_field(data, "build_pull")
|
||||
if build_pull is None:
|
||||
build_pull = _parse_bool_field(data, "pull")
|
||||
except ValueError as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
job_id = _make_job_id(prefix="upd")
|
||||
payload = {
|
||||
"job_id": job_id,
|
||||
"action": "update",
|
||||
"requested_at": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"requested_by": session.get("admin_username") or "",
|
||||
"requested_ip": _request_ip(),
|
||||
"build_no_cache": bool(build_no_cache) if build_no_cache is not None else False,
|
||||
"build_pull": bool(build_pull) if build_pull is not None else False,
|
||||
}
|
||||
write_json_atomic(get_update_request_path(), payload)
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"job_id": job_id,
|
||||
"message": "已提交更新请求,服务将重启(页面可能短暂不可用),请等待1-2分钟后刷新",
|
||||
}
|
||||
), 200
|
||||
Reference in New Issue
Block a user