From e81652497202177cbf915318105f0a239d5900b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sat, 14 Feb 2026 14:47:30 +0800 Subject: [PATCH] 1 --- backend/api/routes/accounts.py | 10 ++++------ backend/api/routes/admin.py | 2 ++ backend/database/models.py | 8 ++++++++ frontend/src/components/AdminDashboard.jsx | 2 +- frontend/src/components/ConfigPanel.jsx | 16 ++++++++-------- frontend/src/services/api.js | 3 ++- 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/backend/api/routes/accounts.py b/backend/api/routes/accounts.py index 0f2ce58..6ccd06a 100644 --- a/backend/api/routes/accounts.py +++ b/backend/api/routes/accounts.py @@ -208,7 +208,7 @@ async def start_service( account_id: int, user: Dict[str, Any] = Depends(get_current_user) ): - """启动交易服务""" + """启动交易服务(需该账号 owner 或管理员)""" require_account_owner(account_id, user) try: program = program_name_for_account(account_id) @@ -235,7 +235,7 @@ async def stop_service( account_id: int, user: Dict[str, Any] = Depends(get_current_user) ): - """停止交易服务""" + """停止交易服务(需该账号 owner 或管理员)""" require_account_owner(account_id, user) try: program = program_name_for_account(account_id) @@ -262,7 +262,7 @@ async def restart_service( account_id: int, user: Dict[str, Any] = Depends(get_current_user) ): - """重启交易服务""" + """重启交易服务(需该账号 owner 或管理员)""" require_account_owner(account_id, user) try: program = program_name_for_account(account_id) @@ -287,9 +287,7 @@ async def restart_service( async def ensure_trading_program(account_id: int, user: Dict[str, Any] = Depends(get_current_user)): if int(account_id) <= 0: raise HTTPException(status_code=400, detail="account_id 必须 >= 1") - # 允许管理员或该账号 owner 执行(owner 用于“我重建配置再启动”) - if (user.get("role") or "user") != "admin": - require_account_owner(int(account_id), user) + require_account_owner(int(account_id), user) sup = ensure_account_program(int(account_id)) if not sup.ok: raise HTTPException(status_code=500, detail=sup.error or "生成 supervisor 配置失败") diff --git a/backend/api/routes/admin.py b/backend/api/routes/admin.py index e0afcae..430649b 100644 --- a/backend/api/routes/admin.py +++ b/backend/api/routes/admin.py @@ -150,6 +150,8 @@ async def grant_user_account(user_id: int, account_id: int, payload: GrantReq, _ if not a: raise HTTPException(status_code=404, detail="账号不存在") try: + if payload.role == "owner": + UserAccountMembership.clear_other_owners_for_account(int(account_id), int(user_id)) UserAccountMembership.add(int(user_id), int(account_id), role=payload.role) except Exception as e: raise HTTPException( diff --git a/backend/database/models.py b/backend/database/models.py index 6826513..29e8bfe 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -217,6 +217,14 @@ class UserAccountMembership: (int(user_id), int(account_id)), ) + @staticmethod + def clear_other_owners_for_account(account_id: int, keep_user_id: int): + """每个账号仅允许一名 owner:将本账号下其他用户的 owner 降为 viewer。""" + db.execute_update( + "UPDATE user_account_memberships SET role = 'viewer' WHERE account_id = %s AND user_id != %s AND role = 'owner'", + (int(account_id), int(keep_user_id)), + ) + @staticmethod def list_for_user(user_id: int): return db.execute_query( diff --git a/frontend/src/components/AdminDashboard.jsx b/frontend/src/components/AdminDashboard.jsx index 126c182..c9bc3e9 100644 --- a/frontend/src/components/AdminDashboard.jsx +++ b/frontend/src/components/AdminDashboard.jsx @@ -187,7 +187,7 @@ const UserAccountGroup = ({ user, allAccounts, onServiceAction }) => { disabled={associating} > - + - - - -
- 提示:若按钮报“无权限”,请让管理员在用户授权里把该账号分配为 owner;若报 supervisor 相关错误,请检查后端对 `/www/server/panel/plugin/supervisor` 的写权限与 supervisorctl 可执行权限。 + 提示:生成配置、启停/重启进程、修改 API 密钥均需该账号的 owner(每账号仅一个)。若报“无权限”请让管理员将该账号分配为 owner;若报 supervisor 相关错误,请检查后端写权限与 supervisorctl。
{accountTradingErr ? (
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index a3185e4..41a257a 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -816,10 +816,11 @@ export const api = { grantUserAccount: async (userId, accountId, role = 'viewer') => { const uid = Number(userId); const aid = Number(accountId); + const roleVal = role === 'owner' ? 'owner' : 'viewer'; const response = await fetch(buildUrl(`/api/admin/users/${uid}/accounts/${aid}`), { method: 'PUT', headers: withAuthHeaders({ 'Content-Type': 'application/json' }), - body: JSON.stringify({ role: role === 'owner' ? 'owner' : 'viewer' }), + body: JSON.stringify({ role: roleVal }), }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: '授权失败' }));