diff --git a/frontend/src/components/AdminDashboard.jsx b/frontend/src/components/AdminDashboard.jsx index 9c370e1..a970554 100644 --- a/frontend/src/components/AdminDashboard.jsx +++ b/frontend/src/components/AdminDashboard.jsx @@ -2,9 +2,14 @@ import React, { useEffect, useState } from 'react' import { api } from '../services/api' import './AdminDashboard.css' -const UserAccountGroup = ({ user, onServiceAction }) => { +const UserAccountGroup = ({ user, allAccounts, onServiceAction }) => { const [expanded, setExpanded] = useState(true) const [processing, setProcessing] = useState(false) + + // 关联管理状态 + const [linkAccountId, setLinkAccountId] = useState('') + const [linkRole, setLinkRole] = useState('viewer') + const [associating, setAssociating] = useState(false) const handleUserAction = async (action) => { if (!window.confirm(`确定要${action === 'start' ? '启动' : '停止'}用户 ${user.username} 下所有账号的交易服务吗?`)) return @@ -17,11 +22,7 @@ const UserAccountGroup = ({ user, onServiceAction }) => { .catch(e => console.error(`Failed to ${action} account ${acc.id}:`, e)) ) await Promise.all(promises) - // 触发刷新(通过父组件回调或简单的延迟刷新,这里父组件会定时刷新,或者我们可以在父组件传递 refresh) - // 这里简单点,父组件会定时刷新,或者我们可以手动触发父组件刷新 - // 由于 onServiceAction 只是单个操作,我们这里最好能通知父组件刷新。 - // 暂时依赖父组件的定时刷新或手动刷新。 - if (onServiceAction) onServiceAction(null, 'refresh') // Hacky way to trigger refresh if supported + if (onServiceAction) onServiceAction(null, 'refresh') } catch (e) { alert(`操作失败: ${e.message}`) } finally { @@ -29,10 +30,39 @@ const UserAccountGroup = ({ user, onServiceAction }) => { } } + const handleGrant = async () => { + if (!linkAccountId) return + setAssociating(true) + try { + await api.grantUserAccount(user.id, linkAccountId, linkRole) + setLinkAccountId('') + if (onServiceAction) onServiceAction(null, 'refresh') + } catch (e) { + alert(`关联失败: ${e.message}`) + } finally { + setAssociating(false) + } + } + + const handleRevoke = async (accountId) => { + if (!window.confirm('确定要取消该账号的关联吗?')) return + try { + await api.revokeUserAccount(user.id, accountId) + if (onServiceAction) onServiceAction(null, 'refresh') + } catch (e) { + alert(`取消关联失败: ${e.message}`) + } + } + // 计算该用户下所有账号的汇总状态 const allRunning = user.accounts.every(a => a.serviceStatus?.running) const allStopped = user.accounts.every(a => !a.serviceStatus?.running) + // 可供关联的账号(排除已关联的) + const availableAccounts = (allAccounts || []).filter(a => + !user.accounts.some(ua => ua.id === a.id) + ) + return (
setExpanded(!expanded)}> @@ -70,6 +100,7 @@ const UserAccountGroup = ({ user, onServiceAction }) => { ID 名称 + 权限 账户状态 服务状态 总资产 @@ -83,6 +114,9 @@ const UserAccountGroup = ({ user, onServiceAction }) => { {acc.id} {acc.name} + + {acc.role || 'viewer'} + {acc.status === 'active' ? '启用' : '禁用'} @@ -126,6 +160,14 @@ const UserAccountGroup = ({ user, onServiceAction }) => { > 停止 +
@@ -133,6 +175,37 @@ const UserAccountGroup = ({ user, onServiceAction }) => { )} + + {/* 关联管理区域 */} +
+ 新增关联: + + + +
)} @@ -149,15 +222,17 @@ const AdminDashboard = () => { // Don't set loading on background refresh (if data exists) if (!data) setLoading(true) - const [usersRes, dashboardRes, servicesRes] = await Promise.all([ + const [usersRes, dashboardRes, servicesRes, accountsRes] = await Promise.all([ api.get('/admin/users/detailed').catch(() => ({ data: [] })), api.getAdminDashboard(), - api.get('/system/trading/services').catch(() => ({ data: { services: [] } })) + api.get('/system/trading/services').catch(() => ({ data: { services: [] } })), + api.get('/accounts').catch(() => ({ data: [] })) ]) const users = usersRes.data || [] const globalStats = dashboardRes // summary, accounts (list of stats) const services = servicesRes.data.services || [] + const allAccounts = accountsRes.data || [] // Index stats and services by account ID const statsMap = {} @@ -194,7 +269,8 @@ const AdminDashboard = () => { setData({ summary: globalStats.summary, - users: enrichedUsers + users: enrichedUsers, + allAccounts }) setError(null) } catch (err) { @@ -265,6 +341,7 @@ const AdminDashboard = () => { ))} diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index da63533..fd1d95b 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -186,24 +186,6 @@ const GlobalConfig = () => { const [loading, setLoading] = useState(true) const [message, setMessage] = useState('') const [busy, setBusy] = useState(false) - const [usersDetailed, setUsersDetailed] = useState([]) - const [accountsAdmin, setAccountsAdmin] = useState([]) - const [linkRole, setLinkRole] = useState('viewer') - const [linkAccountMap, setLinkAccountMap] = useState({}) - const [expandedUserIds, setExpandedUserIds] = useState(new Set()) - - const toggleUserExpand = (userId) => { - setExpandedUserIds(prev => { - const next = new Set(prev) - if (next.has(userId)) { - next.delete(userId) - } else { - next.add(userId) - } - return next - }) - } - // 系统控制相关 const [systemStatus, setSystemStatus] = useState(null) const [backendStatus, setBackendStatus] = useState(null) @@ -1175,131 +1157,7 @@ const GlobalConfig = () => { )} - {/* 用户-账号授权管理(仅管理员) */} - {isAdmin && ( -
-
-

用户-账号授权管理

-

- 管理用户与交易账号的关联权限。用户登录后只能看到被授权的账号。 -

-
- -
- {usersDetailed.length === 0 ? ( -
暂无用户或加载中...
- ) : ( - usersDetailed.map(user => ( -
-
toggleUserExpand(user.id)} - style={{ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: '16px', - cursor: 'pointer', - backgroundColor: expandedUserIds.has(user.id) ? '#f8f9fa' : '#fff' - }} - > -
- - {expandedUserIds.has(user.id) ? '▼' : '▶'} - -
- {user.username} - - {user.role} - -
-
-
-
- 关联账号: {user.accounts ? user.accounts.length : 0} -
-
ID: {user.id}
-
-
- {expandedUserIds.has(user.id) && ( -
- {/* 已关联账号列表 */} -
-
已关联账号:
- {!user.accounts || user.accounts.length === 0 ? ( -
未关联任何账号
- ) : ( -
- {user.accounts.map(acc => ( -
- {acc.name || acc.id} - ({acc.role}) - -
- ))} -
- )} -
- - {/* 添加关联操作区 */} -
- - - - - -
-
- )} -
- )) - )} -
-
- )}