diff --git a/frontend/src/components/AdminDashboard.jsx b/frontend/src/components/AdminDashboard.jsx
index 23ed7a8..fc8ba03 100644
--- a/frontend/src/components/AdminDashboard.jsx
+++ b/frontend/src/components/AdminDashboard.jsx
@@ -126,49 +126,259 @@ const UserAccountGroup = ({ user, allAccounts, onServiceAction }) => {
{acc.serviceStatus ? (
-
- {acc.serviceStatus.state}
-
+
+ {acc.serviceStatus.running ? '运行中' : '停止'}
+
) : (
- UNKNOWN
+ 未启动
)}
|
- {Number(acc.total_balance).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} |
- = 0 ? 'profit' : 'loss'}>
- {Number(acc.total_pnl) > 0 ? '+' : ''}{Number(acc.total_pnl).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
+ | {acc.total_balance?.toFixed(2) || '-'} |
+ = 0 ? 'profit' : 'loss'}>
+ {acc.total_pnl?.toFixed(2) || '-'}
|
- {acc.open_positions} |
+ {acc.open_positions || 0} |
-
- |
+
+ ))}
+
+
+ )}
+
+
+
新增关联
+
+
+
+
+ {associating ? '关联中...' : '关联'}
+
+
+
+
+ )}
+
+ )
+}
+
+const AccountManager = ({ accounts, onRefresh }) => {
+ const [newAccount, setNewAccount] = useState({ name: '', api_key: '', api_secret: '', use_testnet: false, status: 'active' })
+ const [credEditId, setCredEditId] = useState(null)
+ const [credForm, setCredForm] = useState({ api_key: '', api_secret: '', use_testnet: false })
+ const [busy, setBusy] = useState(false)
+ const [message, setMessage] = useState('')
+
+ const notifyAccountsUpdated = () => {
+ try {
+ window.dispatchEvent(new Event('ats:accounts:updated'))
+ } catch (e) {
+ // ignore
+ }
+ }
+
+ const handleCreate = async () => {
+ if (!newAccount.name.trim()) return
+ setBusy(true)
+ setMessage('')
+ try {
+ await api.createAccount(newAccount)
+ setMessage('账号已创建')
+ setNewAccount({ name: '', api_key: '', api_secret: '', use_testnet: false, status: 'active' })
+ if (onRefresh) onRefresh()
+ notifyAccountsUpdated()
+ } catch (e) {
+ setMessage('创建账号失败: ' + (e?.message || '未知错误'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ const handleUpdateStatus = async (account) => {
+ setBusy(true)
+ setMessage('')
+ try {
+ const next = account.status === 'active' ? 'disabled' : 'active'
+ await api.updateAccount(account.id, { status: next })
+ setMessage(`账号 #${account.id} 已${next === 'active' ? '启用' : '禁用'}`)
+ if (onRefresh) onRefresh()
+ notifyAccountsUpdated()
+ } catch (e) {
+ setMessage('更新账号失败: ' + (e?.message || '未知错误'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ const handleUpdateCreds = async () => {
+ if (!credEditId) return
+ setBusy(true)
+ setMessage('')
+ try {
+ const payload = {}
+ if (credForm.api_key) payload.api_key = credForm.api_key
+ if (credForm.api_secret) payload.api_secret = credForm.api_secret
+ payload.use_testnet = !!credForm.use_testnet
+ await api.updateAccountCredentials(credEditId, payload)
+ setMessage(`账号 #${credEditId} 密钥已更新`)
+ setCredEditId(null)
+ if (onRefresh) onRefresh()
+ notifyAccountsUpdated()
+ } catch (e) {
+ setMessage('更新密钥失败: ' + (e?.message || '未知错误'))
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ return (
+
+
系统账号池管理
+ {message &&
{message}
}
+
+
+ {/* Create Account Card */}
+
+
新增账号
+
+
+ setNewAccount({ ...newAccount, name: e.target.value })}
+ placeholder="例如:user_a"
+ />
+
+
+
+ setNewAccount({ ...newAccount, api_key: e.target.value })}
+ />
+
+
+
+ setNewAccount({ ...newAccount, api_secret: e.target.value })}
+ />
+
+
+
+
+
+
+
+
+
+ 创建账号
+
+
+
+ {/* Account List Card */}
+
+
账号列表 ({accounts?.length || 0})
+
+
+
+
+ | ID |
+ 名称 |
+ 状态 |
+ 测试网 |
+ API配置 |
+ 操作 |
+
+
+
+ {(accounts || []).map(a => (
+
+ | #{a.id} |
+ {a.name} |
+
+
+ {a.status === 'active' ? '启用' : '禁用'}
+
+ |
+ {a.use_testnet ? '是' : '否'} |
+
+ {a.has_api_key ? '✅' : '❌'} / {a.has_api_secret ? '✅' : '❌'}
+ |
+
+
+ handleUpdateStatus(a)}
+ title={a.id === 1 ? '默认账号不可禁用' : '切换状态'}
>
- 启动
-
- onServiceAction(acc.id, 'stop')}
- disabled={!acc.serviceStatus?.running}
- className="btn-mini stop"
- >
- 停止
+ {a.status === 'active' ? '禁用' : '启用'}
handleRevoke(acc.id)}
- className="btn-mini danger"
- style={{ marginLeft: '4px' }}
- title="取消关联"
+ className="btn-sm"
+ disabled={busy}
+ onClick={() => {
+ setCredEditId(a.id)
+ setCredForm({ api_key: '', api_secret: '', use_testnet: !!a.use_testnet })
+ }}
>
- ✕
+ 密钥
|
@@ -176,37 +386,45 @@ const UserAccountGroup = ({ user, allAccounts, onServiceAction }) => {
))}
- )}
+
+
+
- {/* 关联管理区域 */}
-
-
新增关联:
-
-
-
- 关联
-
+ {/* Credential Edit Modal/Overlay */}
+ {credEditId && (
+
+
+
更新密钥 (账号 #{credEditId})
+
+
+ setCredForm({ ...credForm, api_key: e.target.value })}
+ />
+
+
+
+ setCredForm({ ...credForm, api_secret: e.target.value })}
+ />
+
+
+
+
+
+ setCredEditId(null)}>取消
+ 保存
+
)}
@@ -354,6 +572,11 @@ const AdminDashboard = () => {
))}
+
+
)
}
diff --git a/frontend/src/components/ConfigPanel.jsx b/frontend/src/components/ConfigPanel.jsx
index a25ea34..7705443 100644
--- a/frontend/src/components/ConfigPanel.jsx
+++ b/frontend/src/components/ConfigPanel.jsx
@@ -42,21 +42,9 @@ const ConfigPanel = () => {
}
}
- // 账号管理(超管)
- const [accountsAdmin, setAccountsAdmin] = useState([])
- const [accountsBusy, setAccountsBusy] = useState(false)
- const [showAccountsAdmin, setShowAccountsAdmin] = useState(false)
- const [newAccount, setNewAccount] = useState({
- name: '',
- api_key: '',
- api_secret: '',
- use_testnet: false,
- status: 'active',
- })
- const [credEditId, setCredEditId] = useState(null)
const [credForm, setCredForm] = useState({ api_key: '', api_secret: '', use_testnet: false })
- // “PCT”类配置里有少数是“百分比数值(<=1表示<=1%)”,而不是“0~1比例”
+ // 预设方案配置
// 例如 LIMIT_ORDER_OFFSET_PCT=0.5 表示 0.5%(而不是 50%)
const PCT_LIKE_KEYS = new Set([
'LIMIT_ORDER_OFFSET_PCT',
@@ -489,23 +477,6 @@ const ConfigPanel = () => {
// 当accountId变化时,重新加载相关数据(避免重复调用,已在onChanged和定时器中处理)
- const loadAccountsAdmin = async () => {
- try {
- const list = await api.getAccounts()
- setAccountsAdmin(Array.isArray(list) ? list : [])
- } catch (e) {
- setAccountsAdmin([])
- }
- }
-
- const notifyAccountsUpdated = () => {
- try {
- window.dispatchEvent(new Event('ats:accounts:updated'))
- } catch (e) {
- // ignore
- }
- }
-
// 注意:accountId 变化时的刷新逻辑已在上面的 useEffect 中处理
const checkFeasibility = async () => {
@@ -1041,272 +1012,7 @@ const ConfigPanel = () => {
) : null}
- {/* 账号管理(超管) */}
- {isAdmin ? (
-
-
-
账号管理(多账号)
-
- {
- setAccountsBusy(true)
- try {
- await loadAccountsAdmin()
- setShowAccountsAdmin((v) => !v)
- } finally {
- setAccountsBusy(false)
- }
- }}
- disabled={accountsBusy}
- title="创建/禁用账号;为每个账号配置独立 API KEY/SECRET;交易/配置/统计会按账号隔离"
- >
- {showAccountsAdmin ? '收起' : '管理账号'}
-
- {
- setAccountsBusy(true)
- try {
- await loadAccountsAdmin()
- notifyAccountsUpdated()
- setMessage('账号列表已刷新')
- } finally {
- setAccountsBusy(false)
- }
- }}
- disabled={accountsBusy}
- >
- 刷新
-
-
-
- {showAccountsAdmin ? (
-
-
-
-
-
账号列表
-
- {(accountsAdmin || []).length ? (
-
-
-
- | ID |
- 名称 |
- 状态 |
- 测试网 |
- API KEY |
- SECRET |
- 操作 |
-
-
-
- {accountsAdmin.map((a) => (
-
- | #{a.id} |
- {a.name || '-'} |
-
-
- {a.status === 'active' ? '启用' : '禁用'}
-
- |
- {a.use_testnet ? '是' : '否'} |
- {a.api_key_masked || (a.has_api_key ? '已配置' : '未配置')} |
- {a.has_api_secret ? '已配置' : '未配置'} |
-
- {
- setAccountsBusy(true)
- setMessage('')
- try {
- const next = a.status === 'active' ? 'disabled' : 'active'
- await api.updateAccount(a.id, { status: next })
- await loadAccountsAdmin()
- notifyAccountsUpdated()
- setMessage(`账号 #${a.id} 已${next === 'active' ? '启用' : '禁用'}`)
- } catch (e) {
- setMessage('更新账号失败: ' + (e?.message || '未知错误'))
- } finally {
- setAccountsBusy(false)
- }
- }}
- >
- {a.status === 'active' ? '禁用' : '启用'}
-
- {
- setCredEditId(a.id)
- setCredForm({ api_key: '', api_secret: '', use_testnet: !!a.use_testnet })
- }}
- >
- 更新密钥
-
- |
-
- ))}
-
-
- ) : (
-
暂无账号(默认账号 #1 会自动存在)
- )}
-
-
-
- {credEditId ? (
-
-
更新账号 #{credEditId} 的密钥
-
-
- ) : null}
-
- ) : null}
-
- ) : null}
{/* 用户提示 */}
{!isAdmin && (