From 6c1bf61ff71648da85379264de5f57107133da11 Mon Sep 17 00:00:00 2001 From: wangjiming Date: Mon, 17 Aug 2026 09:15:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=B9=B3=E5=8F=B0=E6=B2=BB?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/auth.py | 4 +- backend/app/db/platform_store.py | 23 +- backend/app/modules/tenant/router.py | 2 +- docs/governance-user-guide.md | 131 +++--- docs/模型评测功能总结.md | 121 ------ frontend/src/api/modules/resource-acl.ts | 23 ++ frontend/src/api/modules/tenant.ts | 2 +- frontend/src/components/AppSidebar.vue | 26 +- frontend/src/router/index.ts | 22 +- .../src/views/governance/ResourceAclView.vue | 377 ++++++++++++++++++ .../src/views/system/UserSettingsView.vue | 142 +------ .../src/views/tenants/TenantDetailView.vue | 14 +- frontend/src/views/tenants/TenantListView.vue | 13 +- 13 files changed, 561 insertions(+), 339 deletions(-) delete mode 100644 docs/模型评测功能总结.md create mode 100644 frontend/src/api/modules/resource-acl.ts create mode 100644 frontend/src/views/governance/ResourceAclView.vue diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index 1abee7a..155135e 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -178,8 +178,8 @@ def filter_accessible_resource_ids( ).fetchall() accessible = {r["resource_id"] for r in rows} - if resource_type in owner_tables: - table, column = owner_tables[resource_type] + if resource_type in OWNER_TABLES: + table, column = OWNER_TABLES[resource_type] with store.connect() as conn: owned = conn.execute(f"SELECT id FROM {table} WHERE {column}=?", (user_id,)).fetchall() accessible.update(row["id"] for row in owned) diff --git a/backend/app/db/platform_store.py b/backend/app/db/platform_store.py index ba22395..5d5bbf5 100644 --- a/backend/app/db/platform_store.py +++ b/backend/app/db/platform_store.py @@ -1343,6 +1343,8 @@ class PlatformStore: raise ValueError("protected user cannot be deleted") # 级联删除该用户关联的数据 tables_to_clean = [ + # 登录会话(避免残留 session 导致统计显示 user_id) + ("sessions", "user_id=?", [user_id]), # ACL 授权 ("acls", "principal_type='user' AND principal_id=?", [user_id]), # 审批实例(申请人) @@ -3755,12 +3757,16 @@ class PlatformStore: sessions 表列:login_at(TEXT), logout_at(TEXT), duration_seconds(INT)。 优先用 duration_seconds;为空时回退计算 now-login_at(未登出)或 logout_at-login_at。 + + 注意:使用 INNER JOIN 只统计仍存在于 users 表中的用户, + 避免已删除用户的残留 session 记录导致显示 user_id(如 u_xxxx)。 """ with self.connect() as conn: + # 使用 INNER JOIN 而非 LEFT JOIN,确保只统计仍然存在的用户 rows = conn.execute( "SELECT s.user_id, s.login_at, s.logout_at, s.duration_seconds, " "u.username, u.display_name, u.role " - "FROM sessions s LEFT JOIN users u ON s.user_id = u.id " + "FROM sessions s INNER JOIN users u ON s.user_id = u.id " "WHERE s.login_at::timestamptz >= NOW() - make_interval(days => %s)", (days,), ).fetchall() @@ -3768,10 +3774,15 @@ class PlatformStore: agg: dict[str, dict[str, Any]] = {} for r in rows: uid = r["user_id"] or "" + # 优先使用 display_name,其次 username,最后才回退到 user_id + display = r["display_name"] or r["username"] or uid + # 如果回退到了 user_id 格式(说明用户信息不完整),标记为"未知用户" + if display == uid and display.startswith("u_") and len(display) > 10: + display = "(已删除用户)" bucket = agg.setdefault( uid, { - "user": r["display_name"] or r["username"] or uid, + "user": display, "role": r["role"] or "", "total": 0.0, }, @@ -4182,8 +4193,8 @@ class PlatformStore: bucket = grouped.setdefault( key, { - "subject_type": r.get("principal_type"), - "subject_id": r.get("principal_id"), + "principal_type": r.get("principal_type"), + "principal_id": r.get("principal_id"), "permissions": [], }, ) @@ -4201,8 +4212,8 @@ class PlatformStore: for perm in e.get("permissions") or []: flat.append( { - "principal_type": e.get("subject_type"), - "principal_id": e.get("subject_id"), + "principal_type": e.get("principal_type") or e.get("subject_type"), + "principal_id": e.get("principal_id") or e.get("subject_id"), "permission": perm, } ) diff --git a/backend/app/modules/tenant/router.py b/backend/app/modules/tenant/router.py index 73af52e..da60cac 100644 --- a/backend/app/modules/tenant/router.py +++ b/backend/app/modules/tenant/router.py @@ -68,7 +68,7 @@ def update_tenant(tenant_id: str, payload: dict[str, Any] = Body(...), request: def set_quota(tenant_id: str, payload: dict[str, Any] = Body(...), request: Request = None) -> dict[str, Any]: store = get_platform_store() try: - tenant = store.set_tenant_quota(tenant_id, payload.get("quota", {})) + tenant = store.set_tenant_quota(tenant_id, payload) except KeyError: raise fail(404, "tenant not found") store.record_audit( diff --git a/docs/governance-user-guide.md b/docs/governance-user-guide.md index a8ee51d..618a39a 100644 --- a/docs/governance-user-guide.md +++ b/docs/governance-user-guide.md @@ -1,8 +1,9 @@ # 平台治理功能使用指南 -> 版本:v1.0 -> 日期:2026-08-10 +> 版本:v1.1 +> 日期:2026-08-13 > 适用版本:YG Fine-Tune Platform v1.0+ +> 更新说明:移除页面权限码设计,改为基于角色的简化权限模型 --- @@ -39,25 +40,52 @@ ### 1.3 入口在哪里? -所有治理功能集中在左侧导航栏的 **「系统设置」** 分组下: +所有治理功能集中在左侧导航栏的 **「系统设置」** 和 **「平台治理」** 分组下: ``` 系统设置 -├── 用户设置 ← 用户 CRUD + 角色权限 + 密码管理 -├── 租户管理 ← 组织/团队(可选) -├── 项目空间 ← 项目级资源隔离(可选) -├── 审批模板 ← 定义哪些操作需要审批 -├── 审批中心 ← 处理待审批请求 -└── 审计日志 ← 查看所有操作记录 +├── 用户设置 ← 用户 CRUD + 角色权限 + 密码管理(仅 admin) +├── 平台性能 ← 系统监控 +└── 查看日志 ← 日志查看 + +平台治理 +├── 租户管理 ← 组织/团队(仅 admin) +├── 项目空间 ← 项目级资源隔离(仅 admin) +├── 审批模板 ← 定义哪些操作需要审批(仅 admin) +├── 审批中心 ← 处理待审批请求(仅 admin) +└── 审计日志 ← 查看所有操作记录(仅 admin) + +算力资源 +└── 算力节点 ← GPU 分配与管理(仅 admin) ``` > ⚠️ 以上菜单**只有 admin 用户能看到**。普通用户登录后不会出现这些入口。 +> +> **重要变更(v1.1)**:非 admin 用户**默认可以访问所有业务功能菜单**(模型训练、评测、推理、数据集、数据处理等),无需管理员单独分配权限。 --- ## 2. 用户与权限管理 -### 2.1 创建用户 +### 2.1 权限模型概述(v1.1 更新) + +平台采用**基于角色的简化权限模型**: + +| 用户类型 | 可见菜单 | 说明 | +|---------|---------|------| +| **admin(管理员)** | **全部菜单** | 包括用户设置、平台治理、算力节点等管理功能 | +| **非 admin 用户** | **除管理功能外的所有业务菜单** | 模型训练/评测/推理、数据集、数据处理、日志等 | + +> **核心原则**: +> - 非 admin 用户**默认拥有所有业务功能的访问权限**,无需单独分配 +> - 仅以下功能**仅管理员可见**: +> - `用户设置`(用户 CRUD、角色管理) +> - `平台治理`(租户管理、项目空间、审批模板/中心、审计日志) +> - `算力节点`(GPU 分配) +> +> 资源级别的访问控制通过 **ACL(访问控制列表)** 实现,详见第 4 章。 + +### 2.2 创建用户 **路径**:`用户设置` → `创建用户` @@ -71,42 +99,23 @@ - **角色**:选择 `admin` / `operator` / `viewer` 5. 点击保存 -创建后用户可以立即用该账号登录。 +创建后用户可以立即用该账号登录,**无需额外分配页面权限**。 -### 2.2 管理用户权限 +### 2.3 管理员专属功能 -**路径**:`用户设置` → 用户列表 → 操作列「页面权限」 +以下功能**仅 admin 角色可见**,对其他用户隐藏: -#### 给普通用户分配业务模块权限 +| 功能分组 | 包含菜单 | 路由前缀 | +|---------|---------|----------| +| 系统设置 - 用户设置 | 用户列表、创建用户、重置密码 | `/user-settings` | +| 平台治理 - 租户管理 | 租户列表、配额设置 | `/tenants` | +| 平台治理 - 项目空间 | 项目列表、成员管理、ACL | `/projects` | +| 平台治理 - 审批模板 | 审批流程定义 | `/approval-templates` | +| 平台治理 - 审批中心 | 待审批请求处理 | `/approval-instances` | +| 平台治理 - 审计日志 | 操作记录查询与导出 | `/audit-logs` | +| 算力资源 - 算力节点 | GPU 分配与管理 | `/compute` | -点击某用户的「页面权限」按钮,弹出对话框: - -``` -为 张三 分配可访问的页面模块: - -☑ 服务看板 ☑ 模型训练 ☑ 模型评测 -☑ 模型推理 ☑ 模型管理 ☑ 数据集管理 -☐ 数据处理 ☐ 数据类型转换 ☐ 算力节点 ← 不勾选则不可见 -☑ 平台性能 ☑ 查看日志 - -[取消] [保存] -``` - -勾选需要的模块,点「保存」即可。 - -> **注意**: -> - 「用户与权限」这个选项**只有 admin 能看到**,其他用户即使被赋权也不会显示 -> - admin 用户的权限**不可更改**,始终是全选状态且只读 - -#### 限制说明 - -| 权限码 | 说明 | 谁能拥有 | -|---|---|---| -| `user-settings` | 用户设置、租户管理、项目空间、审批、审计日志 | **仅 admin** | -| `compute` | 算力节点、GPU 分配 | **仅 admin** | -| 其他业务权限 | 训练、评测、推理、模型、数据集等 | admin 可分配给任何人 | - -### 2.3 重置用户密码 +### 2.4 重置用户密码 **两种方式**: @@ -120,14 +129,14 @@ 2. 输入旧密码 + 新密码(至少 6 位) 3. 确认修改 -### 2.4 删除用户 +### 2.5 删除用户 **路径**:`用户设置` → 用户列表 → 操作列「删除」 > ⚠️ 删除用户时会**级联清理**其所有关联数据: > - 该用户创建的数据集、基座模型、微调产物、评测任务 > - 该用户的 ACL 授权记录、GPU 分配记录 -> - 该用户的审批实例、审计日志、项目成员关系 +> - 该用户的审批实例、审计日志、项目成员关系、登录会话 > - **训练任务保留不删**(避免算力节点上的物理任务数据不一致) --- @@ -197,7 +206,7 @@ | 资源类型 | admin 看到 | 普通用户看到 | |---|---|---| -| **基座模型**(容器内注册的本地模型) | 全部 | **全部**(共享资源,有 model-manage 权限即可见) | +| **基座模型**(容器内注册的本地模型) | 全部 | **全部**(共享资源) | | **数据集** | 全部 | **自己创建的** + 被 ACL 授权的 | | **微调产物**(训练输出的模型) | 全部 | **自己训练的** + 被 ACL 授权的 | | **评测任务** | 全部 | **自己创建的** + 被 ACL 授权的 | @@ -289,14 +298,14 @@ curl -X PUT /modelTF/resources/dataset/ds_alpaca_id/acl \ ### 5.3 管理审批 -**路径**:`系统设置` → `审批中心` +**路径**:`平台治理` → `审批中心` 1. 查看待审批列表(status=pending) 2. 点击某条记录查看详情 3. 决策:「通过」或「拒绝」 4. 决策结果自动执行对应操作并记录审计日志 -**审批模板**(`系统设置` → `审批模板`):定义每种操作需要几步审批、每步谁来审。默认模板都是单步(admin 审批即可)。 +**审批模板**(`平台治理` → `审批模板`):定义每种操作需要几步审批、每步谁来审。默认模板都是单步(admin 审批即可)。 --- @@ -315,7 +324,7 @@ curl -X PUT /modelTF/resources/dataset/ds_alpaca_id/acl \ ### 6.2 查询审计日志 -**路径**:`系统设置` → `审计日志` +**路径**:`平台治理` → `审计日志` 支持筛选条件: @@ -332,7 +341,7 @@ curl -X PUT /modelTF/resources/dataset/ds_alpaca_id/acl \ ### 6.4 日志保留策略 -审计日志受**留存策略**控制(`系统设置` → 租户管理 → 绑定留存策略)。默认保留 30 天,超期自动清理。 +审计日志受**留存策略**控制(`平台治理` → 租户管理 → 绑定留存策略)。默认保留 30 天,超期自动清理。 --- @@ -340,9 +349,13 @@ curl -X PUT /modelTF/resources/dataset/ds_alpaca_id/acl \ ### Q1: 普通用户看不到某个菜单? -检查两件事: -1. 该用户是否有对应的**权限码**(admin 在「用户设置」→「页面权限」中分配) -2. 该菜单是否属于 **admin 专属**(如「用户设置」「算力节点」——这些对非 admin 永远不可见) +根据 v1.1 权限模型: +1. **业务菜单**(训练、评测、推理、数据集等):普通用户**默认全部可见**,无需分配 +2. **管理菜单**(用户设置、租户管理、算力节点等):**仅 admin 可见**,这是设计如此 + +如果普通用户看不到业务菜单,请检查: +- 用户是否正常登录(token 是否有效) +- 用户状态是否为 `active`(未被停用) ### Q2: 用户创建训练任务时报错"无权使用所选 GPU" @@ -355,7 +368,7 @@ curl -X PUT /modelTF/resources/dataset/ds_alpaca_id/acl \ 正常情况下 `delete_user` 会级联清理关联数据。如果仍有残留: - **训练任务**:设计上保留不删(避免算力节点物理数据不一致),这是预期行为 -- **登录时长排行**:可能来自旧的 session 记录,不影响功能,新登录后会更新 +- **登录时长排行**:可能来自旧的 session 记录(已修复:改用 INNER JOIN 过滤已删除用户) ### Q4: 审批实例一直 pending 没人处理? @@ -394,6 +407,18 @@ curl -H "Authorization: Bearer platform-token-admin" \ 如果是完全忘记且不是 admin,只能由 admin 重置。 +### Q7: 为什么移除了「页面权限」功能?(v1.1 变更说明) + +旧版本要求管理员为每个用户单独分配页面权限码,这导致: +- 新用户创建后需要额外操作才能使用系统 +- 权限配置复杂,容易出错 +- 与实际使用场景不匹配(大多数用户需要访问大部分功能) + +**新模型(v1.1)**简化为: +- 非 admin 用户**默认拥有所有业务功能的访问权限** +- 仅管理员专属功能(用户管理、平台治理、算力节点)受角色限制 +- 资源级别控制通过 ACL 实现,更灵活 + --- ## 附录:API 快速参考 diff --git a/docs/模型评测功能总结.md b/docs/模型评测功能总结.md deleted file mode 100644 index f6ed2d6..0000000 --- a/docs/模型评测功能总结.md +++ /dev/null @@ -1,121 +0,0 @@ -# 模型评测功能总结 - -本项目(基于 LLaMA-Factory 的微调训练平台)包含 **4 套相对独立** 的模型评测能力,分别面向不同的使用场景: - -| 能力 | 入口/目录 | 评测类型 | 打分方式 | -| --- | --- | --- | --- | -| 1. 学术 Benchmark 评测 | `llamafactory/eval/` | 选择题式基准(类 MMLU/C-Eval) | 选项匹配 + few-shot | -| 2. 评估工作台 | `backend/app/api/v1/eval/` | 生成式问答(指令跟随) | BLEU / ROUGE / ExactMatch + 可选 LLM 评审 | -| 3. 平台评估系统 | `backend/app/api/v1/evaluation/` | 基于评估数据集的问答 | 判卷模型(judge model)打分(0–5 分) | -| 4. 训练时验证评估 | `backend/app/services/task_runner.py` | 训练验证集 | loss 指标 | - -下面分别说明。 - ---- - -## 1. 学术 Benchmark 评测(LLaMA-Factory 原生) - -面向标准学术选择题基准(如 MMLU、C-Eval 等),复用 LLaMA-Factory 原生的评测框架。 - -**核心文件** -- `llamafactory/eval/evaluator.py`:`Evaluator` 类 + `run_eval()` 入口 -- `llamafactory/eval/template.py`:评测 prompt 模板(中/英,含 few-shot 示例构建) -- `llamafactory/hparams/evaluation_args.py`:`EvaluationArguments` 配置类 - -**工作流程** -1. 按 `task`(benchmark 名称)加载数据集,按科目(subject)拆分。 -2. 每个样本构造 few-shot 提示词(`n_shot` 控制示例数,由 `lang` 决定中/英模板),将题干与候选选项拼入 prompt。 -3. 调用模型推理得到预测,与标准答案比对,统计每个科目及整体的 `accuracy`。 -4. 结果写入 `save_dir`,打印各科目与平均准确率。 - -**关键参数(`EvaluationArguments`)** -- `task`:基准数据集名 -- `batch_size` / `n_shot` / `lang` / `save_dir` / `seed` -- `model_name_or_path`、`template`、`trust_remote_code` 等模型相关参数 - -> 该能力属于框架底层,本平台前端未直接提供操作入口,主要通过配置文件/脚本调用。 - ---- - -## 2. 评估工作台(生成式评测 + 指标计算) - -后端路由位于 `backend/app/api/v1/eval/__init__.py`,前端称为「评估工作台」。**适用于评测模型的指令跟随与生成质量**,并支持 LLM 作为裁判(LLM-as-a-Judge)。 - -**API 端点** -- `GET /evaluation/tasks`:列出评测任务(`frontend/src/api/evaluation.ts:listTasks`) -- `POST /evaluation/run`:提交一次评测(`runEval`) -- `GET /evaluation/report/{task_id}`:拉取评测报告(`getReport`) -- `DELETE /evaluation/tasks/{task_id}`:删除任务(`deleteTask`) - -**评测流程(`run_eval`)** -1. 通过 **LLaMA-Factory 数据管道**(`get_dataset`) 加载数据集,支持 `subset` 与抽样(`eval_sample`)。 -2. 用 **原生 transformers** 加载模型在本地做生成推理(单进程顺序生成,便于展示样本)。 -3. 计算客观指标(`compute_score`): - - `BLEU`(sacrebleu) - - `ROUGE-1 / ROUGE-2 / ROUGE-L`(rouge-score) - - `Exact Match` -4. **可选 LLM 评审**(judge):当配置了 `judge_model` / `judge_api_base` / `judge_api_key` 时,调用 OpenAI 兼容接口对每条样本打分(10 分制),并输出 4 个维度与理由: - - 核心事实正确性 `factual` - - 信息完整性 `completeness` - - 无幻觉 `no_hallucination` - - 格式合规性 `format` - - 综合分 `score` + `reason` -5. 任务状态持久化在后端 `eval_tasks.json`(支持 running/completed/failed/stopped),前端轮询进度。 - -**前端页面** -- `frontend/src/views/evaluation/EvaluateTask.vue`:任务列表、创建评测对话框(选模型、数据集、指标、可选 judge 配置) -- `frontend/src/views/evaluation/EvaluateReport.vue`:报告页,展示综合得分、BLEU、ROUGE-L、各维度指标及「参考答案 vs 模型预测 vs LLM 评审」对比样例 - ---- - -## 3. 平台评估系统(基于评估数据集 + 判卷模型) - -后端路由位于 `backend/app/api/v1/evaluation/__init__.py`,是平台业务层自研的评测体系。通过「评估数据集」组织题目,可一次性对 **多个被测模型 + 指定判卷模型** 进行批量评分。 - -**核心概念(数据模型 `backend/app/models/models.py`)** -- `EvalDataset`(`models.py:131`):评估数据集,从项目问答对(`Question`/`Chunk`)中按 `question_type`(mixed/fact/reasoning)选题构建,状态 `pending/running/completed/failed`。 -- `EvalResult`(`models.py:147`):单条评测结果,含 `judge_score`(0–5 分)、`is_correct`(true/false/partial)、`feedback`、`expected_answer` 等。 -- `Task`(`models.py:184`):后台任务,`task_type="model-evaluation"`,记录进度与 `model_info`(存放平均分等汇总)。 - -**评测流程(`process_evaluation_task`,`backend/app/services/task_processor.py:336` 起)** -1. 加载评估数据集关联的题目,可选带入 `chunk` 上下文(RAG 场景)。 -2. 对每道题,先用 `build_eval_prompt` 组合「上下文 + 题目 + 参考答案」,调用 **判卷模型**(`call_model`,temperature=0.3)生成评分。 -3. `parse_eval_result` 解析出 `score`(0–5)、`is_correct`、`feedback`,写入 `EvalResult`。 -4. 逐题提交进度(`completed_count` / `progress`),支持中途 `stopped`。 -5. 汇总:`avg_score = 总分/有效数 × 20`(换算百分制),`avg_score_5 = 总分/有效数`(5 分制),存入 `task.model_info`。判定规则:得分 **≥3 视为正确**。 - -**特点** -- 判卷与被测模型解耦:被测模型给出答案,判卷模型(judge)独立评分,降低自评偏差。 -- 支持失败隔离:单题异常写入 `evaluation_status: failed` 记录而不中断整体任务。 - ---- - -## 4. 训练时验证评估 - -在微调训练任务执行期间,由 `backend/app/services/task_runner.py` 的 `do_eval` 触发: - -- 在训练过程中对验证集(validation set)计算 `eval_loss`,用于监控过拟合。 -- 结果回填到 `Task` 的 `loss_info` / `detail`,前端绘制 loss 曲线。 -- 属于训练配套的轻量评估,不参与上述 1–3 的业务评测。 - ---- - -## 附属:前端评测相关页面 - -| 文件 | 作用 | -| --- | --- | -| `frontend/src/views/evaluation/EvaluateTask.vue` | 评估工作台:任务列表 + 创建评测 | -| `frontend/src/views/evaluation/EvaluateReport.vue` | 评估报告:指标卡 + 维度标签 + 对比样例 | -| `frontend/src/api/evaluation.ts` | 评估工作台接口封装 | -| 平台评估系统入口 | 评估数据集管理 + 评估任务(model-evaluation)创建与结果查看 | - ---- - -## 小结 - -- **想要学术榜单式准确率** → 用能力 1(LLaMA-Factory `eval/`)。 -- **想要开放式生成质量(BLEU/ROUGE + LLM 评审)** → 用能力 2(评估工作台 `/evaluation/run`)。 -- **想要基于自有问答数据、用判卷模型批量打分** → 用能力 3(平台评估系统 `model-evaluation` 任务)。 -- **训练过程监控** → 能力 4(`do_eval` 验证集 loss)。 - -三种业务评测(1/2/3)相互独立,可并存于同一平台;数据模型(`EvalDataset`/`EvalResult`/`Task`)主要服务于能力 3,而能力 2 使用独立的 `eval_tasks.json` 文件持久化。 diff --git a/frontend/src/api/modules/resource-acl.ts b/frontend/src/api/modules/resource-acl.ts new file mode 100644 index 0000000..e47c927 --- /dev/null +++ b/frontend/src/api/modules/resource-acl.ts @@ -0,0 +1,23 @@ +import { get, put } from '../request' + +/** ACL 条目 */ +export interface AclEntry { + principal_type: 'user' | 'role' + principal_id: string + permissions: string[] +} + +/** 资源 ACL(含已有条目) */ +export interface ResourceAcl { + resource_type: string + resource_id: string + entries: AclEntry[] +} + +/** 查询资源 ACL */ +export const getResourceAcl = (resourceType: string, resourceId: string) => + get(`/resources/${resourceType}/${resourceId}/acl`) + +/** 设置资源 ACL */ +export const setResourceAcl = (resourceType: string, resourceId: string, entries: AclEntry[]) => + put(`/resources/${resourceType}/${resourceId}/acl`, { entries }) diff --git a/frontend/src/api/modules/tenant.ts b/frontend/src/api/modules/tenant.ts index 630cc61..843fa96 100644 --- a/frontend/src/api/modules/tenant.ts +++ b/frontend/src/api/modules/tenant.ts @@ -30,7 +30,7 @@ export const deleteTenant = (id: string) => del(`/tenants/${id}`) /** 设置租户配额 */ export const setTenantQuota = (id: string, quota: Record) => - put(`/tenants/${id}/quota`, { quota }) + put(`/tenants/${id}/quota`, quota) /** 设置租户留存策略 */ export const setTenantRetention = (id: string, retention_policy_id: string) => diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index f925037..e2a8de9 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -79,6 +79,7 @@ const menuGroups: MenuGroup[] = [ items: [ { key: 'tenants', label: '租户管理', icon: 'fa-building', to: '/tenants', permission: 'user-settings' }, { key: 'projects', label: '项目空间', icon: 'fa-folder', to: '/projects', permission: 'user-settings' }, + { key: 'resource-acl', label: '资源授权', icon: 'fa-key', to: '/resource-acl', permission: 'user-settings' }, { key: 'audit-logs', label: '审计日志', icon: 'fa-history', to: '/audit-logs', permission: 'user-settings' }, { key: 'approval-templates', label: '审批模板', icon: 'fa-list-alt', to: '/approval-templates', permission: 'user-settings' }, { key: 'approval-instances', label: '审批中心', icon: 'fa-check-square', to: '/approval-instances', permission: 'user-settings' }, @@ -94,17 +95,30 @@ const menuGroups: MenuGroup[] = [ }, ] +/** + * 菜单可见性过滤规则(基于 governance-user-guide.md 设计): + * + * 1. admin 用户:可以看到所有菜单 + * 2. 非 admin 用户: + * - 默认可见所有业务菜单(模型训练、评测、推理、数据集、数据处理、转换、性能、日志等) + * - 仅以下菜单对非 admin 不可见: + * - user-settings(用户设置、租户管理、项目空间、审批模板/中心、审计日志) + * - compute(算力节点/GPU 分配) + * + * 注意:移除了旧的权限码(permission code)过滤逻辑, + * 非管理员用户默认拥有所有业务功能的访问权限。 + */ +const ADMIN_ONLY_PERMISSIONS: PermissionCode[] = ['user-settings', 'compute'] + const visibleMenuGroups = computed(() => menuGroups .map((group) => ({ ...group, items: group.items.filter((item) => { - if (!auth.hasPermission(item.permission)) return false - // user-settings 权限对应的菜单仅管理员可见 - if (item.permission === 'user-settings' && !auth.isAdmin) return false - // 算力节点仅管理员可见 - if (item.permission === 'compute' && !auth.isAdmin) return false - return true + // admin 可以看到所有菜单 + if (auth.isAdmin) return true + // 非 admin 用户:仅隐藏管理员专属菜单 + return !ADMIN_ONLY_PERMISSIONS.includes(item.permission) }), })) .filter((group) => group.items.length > 0), diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 53c2eb0..82e14d3 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -74,6 +74,12 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/approvals/ApprovalInstanceView.vue'), meta: { title: '审批中心', permission: 'user-settings' }, }, + { + path: 'resource-acl', + name: 'resource-acl', + component: () => import('@/views/governance/ResourceAclView.vue'), + meta: { title: '资源授权', permission: 'user-settings' }, + }, // 模型调优 { path: 'fine-tune', @@ -351,6 +357,7 @@ const permissionBySegment: Record = { 'audit-logs': 'user-settings', 'approval-templates': 'user-settings', 'approval-instances': 'user-settings', + 'resource-acl': 'user-settings', } function requiredPermission(path: string, explicit?: unknown) { @@ -361,6 +368,11 @@ function requiredPermission(path: string, explicit?: unknown) { // 全局守卫:登录校验 // 离开页面超时由 App.vue 的 visibilitychange 监听接管 +// +// 权限控制规则(基于 governance-user-guide.md 设计): +// - admin 用户:可以访问所有页面 +// - 非 admin 用户:默认可访问所有业务页面(训练、评测、推理、数据等) +// 仅以下页面限制 admin 访问:user-settings、compute(算力节点) router.beforeEach((to, _from, next) => { if (!to.meta.public) routeLoading.value = true const auth = useAuthStore() @@ -384,20 +396,18 @@ router.beforeEach((to, _from, next) => { if (!to.meta.skipPermission) { const permission = requiredPermission(to.path, to.meta.permission) - if (permission && !auth.hasPermission(permission)) { - next({ name: 'permission-denied', replace: true }) - return - } - // user-settings 权限对应的页面仅管理员可访问 + // 仅限制管理员专属页面的访问权限 + // user-settings(用户设置、租户管理、项目空间、审批、审计日志)仅 admin 可访问 if (permission === 'user-settings' && !auth.isAdmin) { next({ name: 'permission-denied', replace: true }) return } - // 算力节点仅管理员可访问 + // compute(算力节点/GPU 分配)仅 admin 可访问 if (permission === 'compute' && !auth.isAdmin) { next({ name: 'permission-denied', replace: true }) return } + // 其他所有业务页面对已登录用户开放,不再检查权限码 } // 路由切换时记录业务模块访问(用于看板用户操作分布统计) diff --git a/frontend/src/views/governance/ResourceAclView.vue b/frontend/src/views/governance/ResourceAclView.vue new file mode 100644 index 0000000..da5783f --- /dev/null +++ b/frontend/src/views/governance/ResourceAclView.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/frontend/src/views/system/UserSettingsView.vue b/frontend/src/views/system/UserSettingsView.vue index e83c067..2200452 100644 --- a/frontend/src/views/system/UserSettingsView.vue +++ b/frontend/src/views/system/UserSettingsView.vue @@ -1,5 +1,5 @@