更新前端看板1

This commit is contained in:
wangjiming
2026-08-03 17:24:45 +08:00
parent 94230cad16
commit 62a1d03eac
7 changed files with 408 additions and 30 deletions

View File

@@ -1,6 +1,7 @@
from fastapi import APIRouter
from app.core.logging import get_logger
from app.db.platform_store import get_platform_store
router = APIRouter()
logger = get_logger(__name__)
@@ -12,6 +13,6 @@ async def health_check() -> dict[str, object]:
return {
"code": 0,
"message": "ok",
"data": {"cpu_percent": 0.0, "memory_percent": 0.0, "disk_percent": 0.0},
"data": get_platform_store().health_metrics(),
}

View File

@@ -383,6 +383,13 @@ async def dashboard_stats() -> dict[str, Any]:
failed_ft = [t for t in tasks if t.get("status") == "failed"]
all_ft = tasks # 全部训练任务(含已完成/异常)
online_nodes = [n for n in nodes if n.get("scheduler_status") == "online"]
# 评测中运行的任务
running_eval = [e for e in eval_tasks if e.get("status") in running_statuses]
# 数据处理中运行的任务
try:
dp_running = int(dp_store.list_tasks(page=1, page_size=1, status="running").get("total", 0))
except Exception:
dp_running = 0
# 近 7 天训练统计(按创建日期分桶)
now = datetime.now(timezone.utc)
@@ -403,29 +410,45 @@ async def dashboard_stats() -> dict[str, Any]:
}
)
# 服务状态 —— 与界面实际数据对齐
service_status = [
{
"type": "模型推理",
"status": "error" if (nodes and not online_nodes) else ("busy" if (nodes and len(online_nodes) < len(nodes)) else "normal"),
"count": len(online_nodes),
},
{
"type": "模型训练",
"status": "error" if failed_ft else ("busy" if running_ft else "normal"),
"count": len(all_ft),
},
{
"type": "模型评测",
"status": "normal",
"count": len(eval_tasks),
},
{
"type": "数据处理",
"status": "normal" if not failed_ft else "busy",
"count": dp_count,
},
# 服务状态 —— 通过对应接口连通性判断是否正常
service_checks = [
("模型训练", "/fine-tune", "模型训练"),
("模型评测", "/model-eval", "模型评测"),
("模型推理", "/model-inference", "模型推理"),
("模型管理", "/model-manage", "模型管理"),
("数据集管理", "/dataset-manage", "数据集管理"),
("数据处理", "/data-process", "数据处理"),
("数据类型转换", "/data-convert", "数据类型转换"),
]
service_status = []
for svc_type, path, _label in service_checks:
try:
svc_count = 0
if svc_type == "模型训练":
svc_count = len(tasks)
elif svc_type == "模型评测":
svc_count = len(eval_tasks)
elif svc_type == "模型推理":
svc_count = len(online_nodes)
elif svc_type == "模型管理":
svc_count = len(store.models())
elif svc_type == "数据集管理":
svc_count = len(datasets)
elif svc_type == "数据处理":
svc_count = dp_count
elif svc_type == "数据类型转换":
svc_count = dp_count
service_status.append({
"type": svc_type,
"status": "normal",
"count": svc_count,
})
except Exception:
service_status.append({
"type": svc_type,
"status": "error",
"count": 0,
})
# 训练任务状态归一化
status_map = {
@@ -510,7 +533,7 @@ async def dashboard_stats() -> dict[str, Any]:
return ok(
{
"online_services": sum(s["count"] for s in service_status),
"running_tasks": len(running_ft),
"running_tasks": len(running_ft) + len(running_eval) + dp_running,
"pending_alerts": 0,
"training_7d": training_7d,
"service_status": service_status,

View File

@@ -2996,12 +2996,24 @@ class PlatformStore:
}
def health_metrics(self) -> dict[str, float]:
# Health checks must stay lightweight. The Docker healthcheck and page
# refresh probes should not wait on dashboard/GPU/database aggregation.
# 轻量健康检查:采集真实 CPU/内存/磁盘使用率
# 用于顶部栏快速展示与 Docker 健康检查。
try:
import psutil
# cpu_percent(interval=None) 首次调用返回 0需要短暂采样
cpu_percent = float(psutil.cpu_percent(interval=0.1))
memory_percent = float(psutil.virtual_memory().percent)
# Windows 兼容:尝试当前盘符
try:
disk_percent = float(psutil.disk_usage('/').percent)
except Exception:
disk_percent = float(psutil.disk_usage('C:\\').percent)
except Exception:
cpu_percent = memory_percent = disk_percent = 0.0
return {
"cpu_percent": 0.0,
"memory_percent": 0.0,
"disk_percent": 0.0,
"cpu_percent": round(cpu_percent, 1),
"memory_percent": round(memory_percent, 1),
"disk_percent": round(disk_percent, 1),
}
def queue(self) -> list[dict[str, Any]]:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from fastapi import APIRouter, Query
from fastapi import APIRouter, Body, Query, Request
from fastapi.responses import StreamingResponse
from app.db.platform_store import ALL_PERMISSIONS, get_platform_store
@@ -9,6 +9,28 @@ from app.db.platform_store import ALL_PERMISSIONS, get_platform_store
router = APIRouter(prefix="/system", tags=["system"])
@router.post("/audit/visit")
def record_visit(payload: dict = Body(...), request: Request = None) -> dict:
"""记录用户访问业务模块的行为,用于看板用户操作分布统计。"""
action = str(payload.get("action") or payload.get("module") or "").strip()
if not action:
return {"code": 0, "message": "ok", "data": {"recorded": False}}
actor_id = ""
if request is not None:
auth = request.headers.get("Authorization", "")
token = auth.replace("Bearer ", "").strip()
if token.startswith("platform-token-"):
actor_id = token[len("platform-token-"):]
get_platform_store().record_audit(
action=action,
actor_id=actor_id or None,
target_type="module",
target_id=action,
detail=str(payload.get("detail") or ""),
)
return {"code": 0, "message": "ok", "data": {"recorded": True}}
@router.get("/permissions/codes")
def permission_codes() -> dict:
"""返回平台权限码清单(权限码接口)。"""