Merge branch 'ft_wyt' of http://www.caoxiaozhu.com:13001/YG-Soft/YG_FT into ft_wyt
This commit is contained in:
@@ -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(),
|
||||
}
|
||||
|
||||
|
||||
@@ -311,10 +311,21 @@ async def _fine_tune_preflight_with_job_payload(
|
||||
|
||||
@router.post("/login")
|
||||
async def login(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
user = get_platform_store().login(payload.get("username", ""), payload.get("password", ""))
|
||||
store = get_platform_store()
|
||||
user = store.login(payload.get("username", ""), payload.get("password", ""))
|
||||
if not user:
|
||||
raise fail(401, "invalid username or password")
|
||||
return ok({"token": f"platform-token-{user['id']}", "user": user})
|
||||
sess = store.create_session(user["id"])
|
||||
return ok({"token": f"platform-token-{user['id']}", "user": user, "session_id": sess["session_id"]})
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
store = get_platform_store()
|
||||
session_id = payload.get("session_id", "")
|
||||
if session_id:
|
||||
store.finish_session(session_id)
|
||||
return ok(None)
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
@@ -372,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)
|
||||
@@ -392,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 = {
|
||||
@@ -444,37 +478,23 @@ async def dashboard_stats() -> dict[str, Any]:
|
||||
for t in tasks[:8]
|
||||
]
|
||||
|
||||
# 用户操作分布:统计平台全部操作(含治理模块)
|
||||
# 用户操作分布:仅统计 模型推理 / 模型训练 / 模型评测 / 数据处理 四类
|
||||
MODULE_LABELS = [
|
||||
("data-process", "数据处理"),
|
||||
("data_process", "数据处理"),
|
||||
("dataset", "数据集管理"),
|
||||
("dataset", "数据处理"),
|
||||
("fine-tune", "模型训练"),
|
||||
("fine_tune", "模型训练"),
|
||||
("model-eval", "模型评测"),
|
||||
("eval", "模型评测"),
|
||||
("model-inference", "模型推理"),
|
||||
("inference", "模型推理"),
|
||||
("model-manage", "模型管理"),
|
||||
("model", "模型管理"),
|
||||
("trained", "模型管理"),
|
||||
# 治理模块操作
|
||||
("tenant", "租户与项目"),
|
||||
("project", "租户与项目"),
|
||||
("approval", "租户与项目"),
|
||||
("acl", "租户与项目"),
|
||||
("user", "用户管理"),
|
||||
("role", "用户管理"),
|
||||
]
|
||||
OP_ORDER = [
|
||||
"数据集管理",
|
||||
"数据处理",
|
||||
"模型训练",
|
||||
"模型评测",
|
||||
"模型推理",
|
||||
"模型管理",
|
||||
"租户与项目",
|
||||
"用户管理",
|
||||
]
|
||||
|
||||
def _op_module(action: str) -> str | None:
|
||||
@@ -507,13 +527,13 @@ async def dashboard_stats() -> dict[str, Any]:
|
||||
for u in recent
|
||||
]
|
||||
|
||||
# 登录时长排行(本月)
|
||||
login_duration_rank = store.login_duration_rank()
|
||||
# 登录时长排行(本月),只取 top 5
|
||||
login_duration_rank = store.login_duration_rank(limit=5)
|
||||
|
||||
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,
|
||||
|
||||
@@ -376,9 +376,9 @@ class PlatformStore:
|
||||
pool_kwargs = {
|
||||
"connect_timeout": 5,
|
||||
"keepalives": 1,
|
||||
"keepalives_idle": 30,
|
||||
"keepalives_interval": 10,
|
||||
"keepalives_count": 5,
|
||||
"keepalives_idle": 10,
|
||||
"keepalives_interval": 5,
|
||||
"keepalives_count": 3,
|
||||
}
|
||||
self._pool = ConnectionPool(
|
||||
conninfo=self.database_url,
|
||||
@@ -3115,12 +3115,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]]:
|
||||
|
||||
@@ -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:
|
||||
"""返回平台权限码清单(权限码接口)。"""
|
||||
|
||||
Reference in New Issue
Block a user