更新前端看板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 fastapi import APIRouter
from app.core.logging import get_logger from app.core.logging import get_logger
from app.db.platform_store import get_platform_store
router = APIRouter() router = APIRouter()
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -12,6 +13,6 @@ async def health_check() -> dict[str, object]:
return { return {
"code": 0, "code": 0,
"message": "ok", "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"] failed_ft = [t for t in tasks if t.get("status") == "failed"]
all_ft = tasks # 全部训练任务(含已完成/异常) all_ft = tasks # 全部训练任务(含已完成/异常)
online_nodes = [n for n in nodes if n.get("scheduler_status") == "online"] 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 天训练统计(按创建日期分桶) # 近 7 天训练统计(按创建日期分桶)
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
@@ -403,29 +410,45 @@ async def dashboard_stats() -> dict[str, Any]:
} }
) )
# 服务状态 —— 与界面实际数据对齐 # 服务状态 —— 通过对应接口连通性判断是否正常
service_status = [ service_checks = [
{ ("模型训练", "/fine-tune", "模型训练"),
"type": "模型推理", ("模型评测", "/model-eval", "模型评测"),
"status": "error" if (nodes and not online_nodes) else ("busy" if (nodes and len(online_nodes) < len(nodes)) else "normal"), ("模型推理", "/model-inference", "模型推理"),
"count": len(online_nodes), ("模型管理", "/model-manage", "模型管理"),
}, ("数据集管理", "/dataset-manage", "数据集管理"),
{ ("数据处理", "/data-process", "数据处理"),
"type": "模型训练", ("数据类型转换", "/data-convert", "数据类型转换"),
"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_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 = { status_map = {
@@ -510,7 +533,7 @@ async def dashboard_stats() -> dict[str, Any]:
return ok( return ok(
{ {
"online_services": sum(s["count"] for s in service_status), "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, "pending_alerts": 0,
"training_7d": training_7d, "training_7d": training_7d,
"service_status": service_status, "service_status": service_status,

View File

@@ -2996,12 +2996,24 @@ class PlatformStore:
} }
def health_metrics(self) -> dict[str, float]: def health_metrics(self) -> dict[str, float]:
# Health checks must stay lightweight. The Docker healthcheck and page # 轻量健康检查:采集真实 CPU/内存/磁盘使用率
# refresh probes should not wait on dashboard/GPU/database aggregation. # 用于顶部栏快速展示与 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 { return {
"cpu_percent": 0.0, "cpu_percent": round(cpu_percent, 1),
"memory_percent": 0.0, "memory_percent": round(memory_percent, 1),
"disk_percent": 0.0, "disk_percent": round(disk_percent, 1),
} }
def queue(self) -> list[dict[str, Any]]: def queue(self) -> list[dict[str, Any]]:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from fastapi import APIRouter, Query from fastapi import APIRouter, Body, Query, Request
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from app.db.platform_store import ALL_PERMISSIONS, get_platform_store 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 = 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") @router.get("/permissions/codes")
def permission_codes() -> dict: def permission_codes() -> dict:
"""返回平台权限码清单(权限码接口)。""" """返回平台权限码清单(权限码接口)。"""

View File

@@ -0,0 +1,5 @@
import { post } from '../request'
/** 记录用户访问某个业务模块(用于看板用户操作分布统计) */
export const recordModuleVisit = (module: string, detail?: string) =>
post('/system/audit/visit', { action: module, detail: detail || '' })

View File

@@ -1,6 +1,37 @@
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios' import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
/**
* 用户操作分布:哪些模块路径算"业务操作"(用于看板统计)
* 请求命中这些路径时,会自动调用 record_visit 记录一次(同一模块 60 秒内去重)
*/
const VISIT_TRACKED_PREFIXES: Array<[string, string]> = [
['/fine-tune', 'fine-tune'],
['/model-eval', 'model-eval'],
['/model-inference', 'model-inference'],
['/data-process', 'data-process'],
['/data-convert', 'data-convert'],
['/model-manage', 'model-manage'],
['/dataset-manage', 'dataset'],
]
function trackVisit(url: string | undefined) {
if (!url) return
for (const [prefix, module] of VISIT_TRACKED_PREFIXES) {
if (url.includes(prefix)) {
const key = `visit:${module}`
const last = Number(sessionStorage.getItem(key) || 0)
if (Date.now() - last < 60000) return // 60 秒内去重
sessionStorage.setItem(key, String(Date.now()))
// fire-and-forget 调用后端记录接口
import('./modules/audit-visit').then(({ recordModuleVisit }) => {
recordModuleVisit(module, url).catch(() => { /* ignore */ })
}).catch(() => { /* ignore */ })
return
}
}
}
/** /**
* 后端统一响应格式 * 后端统一响应格式
* code === 0 表示成功data 为业务数据 * code === 0 表示成功data 为业务数据
@@ -60,6 +91,8 @@ service.interceptors.response.use(
return response return response
} }
if (res.code === 0) { if (res.code === 0) {
// 记录业务模块访问(用于看板用户操作分布统计)
trackVisit(response.config.url)
return res.data return res.data
} }
// 业务错误 // 业务错误

282
平台治理.md Normal file
View File

@@ -0,0 +1,282 @@
# 平台治理功能说明
平台治理涵盖用户管理、租户管理、项目管理、审批管理、审计日志、资源授权(ACL)、留存策略等企业管理能力。功能入口位于侧边栏「平台治理」和「系统设置」两个分组下。
本平台核心业务是**模型微调**:用户上传数据 → 数据处理 → 模型训练 → 模型评测 → 模型推理。平台治理负责管理**谁**能访问**哪个租户/项目**的**哪些资源**。
---
## 侧边栏菜单结构
```
平台治理
├── 租户管理 /tenants
├── 项目空间 /projects
├── 审计日志 /audit-logs
├── 审批模板 /approval-templates
└── 审批中心 /approval-instances
系统设置
├── 用户设置 /user-settings
├── 平台性能 /hardware
└── 查看日志 /logs
```
---
## 1. 租户管理
**对应页面**:列表页 `TenantListView.vue` + 详情页 `TenantDetailView.vue`
**后端接口**`GET/POST /tenants``GET/PUT/DELETE /tenants/:id``PUT /tenants/:id/quota``PUT /tenants/:id/retention-policy`
### 列表页
**展示列**租户名称、用户ID、状态、创建时间
**行操作**
| 按钮 | 功能 |
|------|------|
| 详情 | 跳转详情页 `/tenants/:id` |
| 删除 | 确认弹窗后调用 `DELETE /tenants/:id` |
**新建租户弹窗**
| 字段 | 控件 | 说明 |
|------|------|------|
| 名称 | 文本输入 | 必填 |
| 用户ID | 文本输入 | 可选 |
| GPU 数量 | 数字输入 | 配额0=不限制 |
| 存储配额(GB) | 数字输入 | 配额0=不限制 |
| 最大项目数 | 数字输入 | 配额0=不限制 |
### 详情页
**基本信息**名称、用户ID、状态、创建时间、配额友好格式显示`GPU 8 | 存储 100GB | 项目 5`
**配额编辑**GPU 数量 / 存储配额(GB) / 最大项目数,三个数字输入 + 保存按钮
**已移除**:详情页中不再内嵌项目空间列表(项目有独立页面)
### 当前缺失
- [ ] 编辑租户名称和用户ID`updateTenant` API 已有UI 缺失)
- [ ] 留存策略关联设置(`setTenantRetention` API 已有UI 缺失)
- [ ] 列表页无搜索/筛选
- [ ] 状态列无 Tag 着色
---
## 2. 项目空间
**对应页面**:列表页 `ProjectListView.vue` + 详情页 `ProjectDetailView.vue`
**后端接口**`GET/POST /projects``GET/PUT/DELETE /projects/:id``GET/POST/DELETE /projects/:id/members``PUT /projects/:id/archive`
### 列表页
**展示列**项目名、编码ID、状态、描述、创建时间
**工具栏**
- 租户选择器(下拉过滤,按租户编码选择)
- 搜索框(按项目名/编码搜索)
- 新建项目按钮
**新建项目弹窗**
| 字段 | 控件 | 说明 |
|------|------|------|
| 名称 | 文本输入 | 必填 |
| 编码ID | 下拉选择 | 选择已有租户的编码(如 `default`),关联到该租户 |
| 描述 | 文本域 | 可选3行 |
**行操作**:详情 + 删除
### 详情页
**基本信息**:名称、编码、状态、租户、描述、创建时间
**项目成员管理**
- 成员列表(用户名、角色、添加时间)
- 添加成员:选择用户 + 角色member/admin/viewer
- 移除成员
**资源授权(ACL)**
- 弹窗编辑器,逐条配置授权规则
- 主体类型用户下拉选已有用户或项目角色member/admin/viewer
- 权限read / write / execute / download / delete / share多选 checkbox
- 增删行后统一保存
### 当前缺失
- [ ] 编辑项目基本信息(名称、描述)
- [ ] 项目启停/归档操作(`archiveProject` API 已有UI 缺失)
- [ ] 项目下的资源使用统计(模型数、数据集数、任务数)
---
## 3. 审批管理
**对应页面**`ApprovalTemplateView.vue`(模板)+ `ApprovalInstanceView.vue`(实例)
**后端接口**:模板 CRUD、实例列表+决策
### 审批模板
**展示列**:模板名、步骤数、创建时间
**新建模板**
| 字段 | 控件 | 说明 |
|------|------|------|
| 模板名称 | 文本输入 | 必填 |
| 审批步骤 | 文本域 | **需手写 JSON 字符串**,体验差 |
**当前缺失**
- [ ] 可视化步骤编辑(拖拽添加步骤、选审批人)
- [ ] 模板编辑/删除API 已有UI 缺失)
- [ ] 模板详情页
### 审批中心
**展示列**资源类型、资源ID、状态Tag着色、发起人、创建时间
**工具栏**:状态筛选(待审批/已通过/已拒绝)、资源类型/ID 搜索
**行操作**:通过/拒绝(弹窗填写审批意见)
**当前缺失**
- [ ] "我发起的"审批视角
- [ ] 审批流转详情(谁审批了、什么时间)
- [ ] 撤回功能
---
## 4. 审计日志
**对应页面**`AuditLogView.vue`
**后端接口**`GET /system/audit-logs`(多条件筛选+分页)、`GET /system/audit-logs/export`CSV导出
### 列表页
**展示列**时间、租户ID、项目ID、操作人ID、动作、目标类型、目标ID、详情、IP
**筛选条件**租户ID、项目ID、操作人ID、动作、目标类型、开始时间、结束时间
**工具栏**CSV 导出按钮(最多 10000 条)
### 当前缺失
- [ ] 分页控件(数据量大时需要翻页)
- [ ] 筛选字段改为下拉选择(当前全是文本输入,不知道有哪些可选值)
- [ ] 详情弹窗(点击某条记录查看完整信息)
---
## 5. 用户设置
**对应页面**`UserSettingsView.vue`(列表)+ `UserCreateView.vue`(创建)+ `UserPermissionView.vue`(权限弹窗)
**后端接口**`GET/POST/PUT/DELETE /users``POST /users/:id/reset-password`
### 列表页
**展示列**:账号、显示名、角色、状态、页面权限、创建时间
**行操作**
| 操作 | 说明 |
|------|------|
| 启停开关 | protected 用户和自己不可操作 |
| 重置密码 | 弹窗确认,默认密码 `Platform@123` |
| 页面权限 | 弹窗 checkbox 组12 个模块可选:看板/模型训练/模型评测/模型推理/模型管理/数据集/数据处理/数据转换/算力/平台性能/查看日志/用户设置 |
| 删除 | 确认弹窗protected 用户和自己不可操作 |
### 创建页
**表单字段**:账号、显示名、初始密码、角色(超级管理员/操作员/观察员)、状态、页面权限
### 当前缺失
- [ ] 用户编辑页面(修改显示名、角色等,`updateUser` API 已有UI 缺失)
- [ ] 权限码显示为英文(如 `fine-tune`),无中文翻译
---
## 6. 平台性能
**对应页面**`HardwareView.vue`
**后端接口**`GET /system-info``GET /health`(顶部栏实时指标)
### 页面内容
**概览卡片**CPU型号/核心数/使用率)、内存(已用/总量/使用率)、磁盘(已用/总量/使用率)、网络吞吐
**趋势图**CPU/内存/磁盘/GPU 利用率折线图(最近 60 次采样1/3/5/10秒自动刷新
**GPU 资源池**:每张卡展示名称、状态、利用率、显存、温度、功耗、风扇转速;点击卡片查看详情抽屉(设备属性、趋势图、进程列表)
**主机信息**OS、运行时长、进程数、GPU 驱动版本
### 当前缺失
- [ ] 历史数据持久化(刷新后采样清空)
- [ ] 告警阈值设置
- [ ] 网络吞吐数据显示为空
---
## 7. 查看日志
**对应页面**`LogsView.vue`(系统日志 + 训练日志双 Tab
### 系统日志
- 按日期选择日志文件
- 关键词搜索
- 日志级别筛选INFO/WARN/ERROR/DEBUG
- 自动刷新5/10/30/60 秒可调)
### 训练日志
- 按 PID 选择训练日志文件
- 同样支持搜索和级别筛选
### 当前缺失
- [ ] 日志下载/导出
- [ ] 分页或虚拟滚动(大文件加载慢)
- [ ] 行号显示
---
## 8. 留存策略 ⚠️ 前端完全缺失
**后端 API 已完整实现**CRUD 5 个端点),**前端 `retention.ts` 模块已封装**,但:
- 侧边栏无菜单入口
- 路由未配置
- 无任何 Vue 页面
---
## 数据流转关系
```
用户登录 → 分配角色(admin/operator/viewer) + 页面权限
├─ 创建租户配额GPU/存储/项目数)
│ └─ 关联用户
├─ 创建项目关联租户选择编码ID
│ ├─ 项目成员角色member/admin/viewer
│ └─ 资源授权(ACL):谁对什么资源有什么权限
├─ 创建审批模板(定义审批流程)
│ └─ 审批实例:敏感操作需要审批(通过/拒绝)
├─ 审计日志:所有操作自动记录(谁在什么时间做了什么)
└─ 留存策略:定义数据保留周期,自动清理过期数据
```
---
## 完成度总表
| 模块 | 列表 | 新建 | 编辑 | 删除 | 搜索 | 筛选 | 导出 | 完成度 |
|------|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| 租户管理 | ✅ | ✅ | 仅配额 | ✅ | ❌ | ❌ | ❌ | 75% |
| 项目空间 | ✅ | ✅ | ❌ | ✅ | ✅ | 按租户 | ❌ | 80% |
| 审批模板 | ✅ | ✅(JSON) | ❌ | ❌ | ❌ | ❌ | ❌ | 45% |
| 审批中心 | ✅ | N/A | N/A | ❌ | ✅ | 按状态 | ❌ | 65% |
| 审计日志 | ✅ | N/A | N/A | N/A | ❌ | 多字段 | CSV | 70% |
| 用户设置 | ✅ | ✅ | 仅权限 | ✅ | ❌ | ❌ | ❌ | 75% |
| 平台性能 | ✅ | N/A | N/A | N/A | N/A | N/A | ❌ | 80% |
| 查看日志 | ✅ | N/A | N/A | N/A | 关键词 | 按级别 | ❌ | 70% |
| **留存策略** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **0%** |