修复普通用户对模型训练的权限操作
This commit is contained in:
@@ -225,12 +225,16 @@ def _require_approval_or_admin(
|
|||||||
"""
|
"""
|
||||||
高风险操作审批旁路:
|
高风险操作审批旁路:
|
||||||
- admin 用户直接放行(返回 None)
|
- admin 用户直接放行(返回 None)
|
||||||
- 普通用户创建审批实例,返回审批待定响应(code=202,非 None)
|
- 资源创建者(Owner)直接放行(返回 None)
|
||||||
|
- 其他普通用户创建审批实例,返回审批待定响应(code=202,非 None)
|
||||||
code=202 使前端响应拦截器走业务错误分支,弹提示并 reject,
|
code=202 使前端响应拦截器走业务错误分支,弹提示并 reject,
|
||||||
避免前端误认为删除成功。
|
避免前端误认为删除成功。
|
||||||
"""
|
"""
|
||||||
if is_admin(current_user):
|
if is_admin(current_user):
|
||||||
return None
|
return None
|
||||||
|
# 资源创建者直接放行,无需审批
|
||||||
|
if _check_owner(resource_type, resource_id, current_user.get("id")):
|
||||||
|
return None
|
||||||
store = get_platform_store()
|
store = get_platform_store()
|
||||||
instance = store.create_approval_instance({
|
instance = store.create_approval_instance({
|
||||||
"resource_type": resource_type,
|
"resource_type": resource_type,
|
||||||
@@ -245,6 +249,28 @@ def _require_approval_or_admin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _check_owner(resource_type: str, resource_id: str, user_id: str | None) -> bool:
|
||||||
|
"""直接查数据库判断 user_id 是否为资源的 created_by。"""
|
||||||
|
from app.core.auth import OWNER_TABLES
|
||||||
|
table_info = OWNER_TABLES.get(resource_type)
|
||||||
|
if not table_info:
|
||||||
|
return False
|
||||||
|
table, column = table_info
|
||||||
|
store = get_platform_store()
|
||||||
|
with store.connect() as conn:
|
||||||
|
row = conn.execute(f"SELECT {column} FROM {table} WHERE id=?", (resource_id,)).fetchone()
|
||||||
|
if not row:
|
||||||
|
return False
|
||||||
|
owner = row[column]
|
||||||
|
if column == "payload":
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
owner = json.loads(owner or "{}").get("created_by")
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
owner = None
|
||||||
|
return owner == user_id
|
||||||
|
|
||||||
|
|
||||||
def _node_for_task(task: dict[str, Any]) -> dict[str, Any] | None:
|
def _node_for_task(task: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
return next((node for node in get_platform_store().compute_nodes() if node["id"] == task.get("compute_node_id")), None)
|
return next((node for node in get_platform_store().compute_nodes() if node["id"] == task.get("compute_node_id")), None)
|
||||||
|
|
||||||
@@ -1457,8 +1483,11 @@ async def fine_tune_list(current_user: dict = Depends(get_current_user)) -> dict
|
|||||||
tasks = get_platform_store().tasks()
|
tasks = get_platform_store().tasks()
|
||||||
if current_user.get("role") == "admin" or current_user.get("protected"):
|
if current_user.get("role") == "admin" or current_user.get("protected"):
|
||||||
return ok(tasks)
|
return ok(tasks)
|
||||||
|
# 普通用户可见:自己创建的 + ACL 授权的
|
||||||
|
user_id = current_user.get("id")
|
||||||
accessible = set(filter_accessible_resource_ids("fine-tune", [t["id"] for t in tasks], current_user))
|
accessible = set(filter_accessible_resource_ids("fine-tune", [t["id"] for t in tasks], current_user))
|
||||||
return ok([t for t in tasks if t["id"] in accessible])
|
result = [t for t in tasks if t.get("created_by") == user_id or t["id"] in accessible]
|
||||||
|
return ok(result)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/fine-tune")
|
@router.post("/fine-tune")
|
||||||
@@ -1740,8 +1769,11 @@ async def model_eval_list(current_user: dict = Depends(get_current_user)) -> dic
|
|||||||
tasks = get_platform_store().eval_tasks()
|
tasks = get_platform_store().eval_tasks()
|
||||||
if current_user.get("role") == "admin" or current_user.get("protected"):
|
if current_user.get("role") == "admin" or current_user.get("protected"):
|
||||||
return ok(tasks)
|
return ok(tasks)
|
||||||
|
# 普通用户可见:自己创建的 + ACL 授权的
|
||||||
|
user_id = current_user.get("id")
|
||||||
accessible = set(filter_accessible_resource_ids("eval", [t["id"] for t in tasks], current_user))
|
accessible = set(filter_accessible_resource_ids("eval", [t["id"] for t in tasks], current_user))
|
||||||
return ok([t for t in tasks if t["id"] in accessible])
|
result = [t for t in tasks if t.get("created_by") == user_id or t["id"] in accessible]
|
||||||
|
return ok(result)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/model-eval/{task_id}")
|
@router.get("/model-eval/{task_id}")
|
||||||
@@ -1777,6 +1809,7 @@ async def model_eval_start(payload: dict[str, Any] = Body(...), current_user: di
|
|||||||
"""Start an evaluation task: submit eval job to compute node."""
|
"""Start an evaluation task: submit eval job to compute node."""
|
||||||
store = get_platform_store()
|
store = get_platform_store()
|
||||||
# 1. Create eval task record
|
# 1. Create eval task record
|
||||||
|
payload.setdefault("created_by", current_user.get("id"))
|
||||||
task = store.create_eval_task({**payload, "status": "pending"})
|
task = store.create_eval_task({**payload, "status": "pending"})
|
||||||
|
|
||||||
# 2. Resolve model path (supports both regular models and trained models)
|
# 2. Resolve model path (supports both regular models and trained models)
|
||||||
@@ -1978,8 +2011,11 @@ async def model_compare_list(current_user: dict = Depends(get_current_user)) ->
|
|||||||
tasks = get_platform_store().compare_tasks()
|
tasks = get_platform_store().compare_tasks()
|
||||||
if is_admin(current_user):
|
if is_admin(current_user):
|
||||||
return ok(tasks)
|
return ok(tasks)
|
||||||
|
# 普通用户可见:自己创建的 + ACL 授权的
|
||||||
|
user_id = current_user.get("id")
|
||||||
accessible = filter_accessible_resource_ids_batch("compare", [item["id"] for item in tasks], current_user)
|
accessible = filter_accessible_resource_ids_batch("compare", [item["id"] for item in tasks], current_user)
|
||||||
return ok([item for item in tasks if item["id"] in accessible])
|
result = [item for item in tasks if item.get("created_by") == user_id or item["id"] in accessible]
|
||||||
|
return ok(result)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/model-compare")
|
@router.post("/model-compare")
|
||||||
|
|||||||
@@ -570,6 +570,16 @@ class PlatformStore:
|
|||||||
""")
|
""")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # 列不存在时忽略
|
pass # 列不存在时忽略
|
||||||
|
# 修复历史数据:为 eval_tasks 表回填 created_by(从 payload JSON 中提取)
|
||||||
|
try:
|
||||||
|
conn.execute("""
|
||||||
|
UPDATE eval_tasks SET created_by = payload::json->>'created_by'
|
||||||
|
WHERE (created_by IS NULL OR created_by = '')
|
||||||
|
AND payload IS NOT NULL
|
||||||
|
AND payload::json->>'created_by' IS NOT NULL
|
||||||
|
""")
|
||||||
|
except Exception:
|
||||||
|
pass # 列或语法不支持时忽略
|
||||||
# 清理超过 7 天的操作日志
|
# 清理超过 7 天的操作日志
|
||||||
try:
|
try:
|
||||||
cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
|
cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
|
||||||
@@ -2031,6 +2041,7 @@ class PlatformStore:
|
|||||||
"process_id": None,
|
"process_id": None,
|
||||||
"train_duration": "",
|
"train_duration": "",
|
||||||
"create_time": now,
|
"create_time": now,
|
||||||
|
"created_by": payload.get("created_by"),
|
||||||
}
|
}
|
||||||
with self.connect() as conn:
|
with self.connect() as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@@ -2529,8 +2540,8 @@ class PlatformStore:
|
|||||||
if data.get("metric") == "custom":
|
if data.get("metric") == "custom":
|
||||||
data["metric"] = data["metric_label"]
|
data["metric"] = data["metric_label"]
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO eval_tasks (id, name, payload, status, create_time) VALUES (?, ?, ?, ?, ?)",
|
"INSERT INTO eval_tasks (id, name, payload, status, create_time, created_by) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
(task_id, name, json_dumps(data), status, now),
|
(task_id, name, json_dumps(data), status, now, data.get("created_by")),
|
||||||
)
|
)
|
||||||
return self.eval_task(task_id)
|
return self.eval_task(task_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||||
@@ -272,9 +272,14 @@ async function handleSubmit() {
|
|||||||
await startFineTune({ ...payload, task_id: taskId })
|
await startFineTune({ ...payload, task_id: taskId })
|
||||||
ElMessage.success('训练任务已创建并启动')
|
ElMessage.success('训练任务已创建并启动')
|
||||||
} catch {
|
} catch {
|
||||||
|
try {
|
||||||
await updateFineTune(taskId, { status: 'failed' })
|
await updateFineTune(taskId, { status: 'failed' })
|
||||||
|
} catch {
|
||||||
|
// 忽略状态更新失败
|
||||||
|
}
|
||||||
ElMessage.error('任务已创建,但训练启动失败')
|
ElMessage.error('任务已创建,但训练启动失败')
|
||||||
}
|
}
|
||||||
|
// 无论启动成功还是失败,都跳转到训练列表
|
||||||
router.push('/fine-tune')
|
router.push('/fine-tune')
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('训练任务创建失败,请稍后重试')
|
ElMessage.error('训练任务创建失败,请稍后重试')
|
||||||
|
|||||||
Reference in New Issue
Block a user