1
This commit is contained in:
parent
9f21cc1d02
commit
efc88b2083
|
|
@ -126,49 +126,259 @@ const UserAccountGroup = ({ user, allAccounts, onServiceAction }) => {
|
|||
</td>
|
||||
<td>
|
||||
{acc.serviceStatus ? (
|
||||
<span className={`status-badge`}
|
||||
style={{
|
||||
backgroundColor: acc.serviceStatus.running ? '#e8f5e9' : '#ffebee',
|
||||
color: acc.serviceStatus.running ? '#2e7d32' : '#c62828',
|
||||
border: `1px solid ${acc.serviceStatus.running ? '#c8e6c9' : '#ffcdd2'}`,
|
||||
padding: '2px 8px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '12px'
|
||||
}}>
|
||||
{acc.serviceStatus.state}
|
||||
</span>
|
||||
<span className={`status-badge ${acc.serviceStatus.running ? 'running' : 'stopped'}`}>
|
||||
{acc.serviceStatus.running ? '运行中' : '停止'}
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ color: '#999' }}>UNKNOWN</span>
|
||||
<span className="status-badge stopped">未启动</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{Number(acc.total_balance).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
|
||||
<td className={Number(acc.total_pnl) >= 0 ? 'profit' : 'loss'}>
|
||||
{Number(acc.total_pnl) > 0 ? '+' : ''}{Number(acc.total_pnl).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
<td>{acc.total_balance?.toFixed(2) || '-'}</td>
|
||||
<td className={acc.total_pnl >= 0 ? 'profit' : 'loss'}>
|
||||
{acc.total_pnl?.toFixed(2) || '-'}
|
||||
</td>
|
||||
<td>{acc.open_positions}</td>
|
||||
<td>{acc.open_positions || 0}</td>
|
||||
<td>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button
|
||||
onClick={() => onServiceAction(acc.id, 'start')}
|
||||
disabled={acc.serviceStatus?.running}
|
||||
className="btn-mini start"
|
||||
<button
|
||||
className="btn-icon"
|
||||
onClick={() => handleServiceAction(acc.id, acc.serviceStatus?.running ? 'stop' : 'start')}
|
||||
title={acc.serviceStatus?.running ? '停止服务' : '启动服务'}
|
||||
>
|
||||
{acc.serviceStatus?.running ? '⏹' : '▶'}
|
||||
</button>
|
||||
<button
|
||||
className="btn-icon danger"
|
||||
onClick={() => handleRevoke(acc.id)}
|
||||
title="取消关联"
|
||||
style={{ marginLeft: '8px' }}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
<div className="add-account-section">
|
||||
<h4>新增关联</h4>
|
||||
<div className="add-account-form">
|
||||
<select
|
||||
value={linkAccountId}
|
||||
onChange={e => setLinkAccountId(e.target.value)}
|
||||
disabled={associating}
|
||||
>
|
||||
<option value="">选择账号...</option>
|
||||
{availableAccounts.map(a => (
|
||||
<option key={a.id} value={a.id}>
|
||||
#{a.id} {a.name} ({a.status === 'active' ? '启用' : '禁用'})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={linkRole}
|
||||
onChange={e => setLinkRole(e.target.value)}
|
||||
disabled={associating}
|
||||
>
|
||||
<option value="viewer">观察者 (Viewer)</option>
|
||||
<option value="trader">交易员 (Trader)</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={handleGrant}
|
||||
disabled={!linkAccountId || associating}
|
||||
className="btn-primary"
|
||||
>
|
||||
{associating ? '关联中...' : '关联'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="accounts-manager-section">
|
||||
<h3>系统账号池管理</h3>
|
||||
{message && <div className={`message ${message.includes('失败') ? 'error' : 'success'}`}>{message}</div>}
|
||||
|
||||
<div className="accounts-manager-grid">
|
||||
{/* Create Account Card */}
|
||||
<div className="card create-account-card">
|
||||
<h4>新增账号</h4>
|
||||
<div className="form-group">
|
||||
<label>名称</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newAccount.name}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, name: e.target.value })}
|
||||
placeholder="例如:user_a"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>API KEY (可选)</label>
|
||||
<input
|
||||
type="password"
|
||||
value={newAccount.api_key}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, api_key: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>API SECRET (可选)</label>
|
||||
<input
|
||||
type="password"
|
||||
value={newAccount.api_secret}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, api_secret: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group checkbox">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!newAccount.use_testnet}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, use_testnet: e.target.checked })}
|
||||
/>
|
||||
测试网
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>初始状态</label>
|
||||
<select
|
||||
value={newAccount.status}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, status: e.target.value })}
|
||||
>
|
||||
<option value="active">启用</option>
|
||||
<option value="disabled">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
className="btn-primary full-width"
|
||||
onClick={handleCreate}
|
||||
disabled={busy || !newAccount.name.trim()}
|
||||
>
|
||||
创建账号
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Account List Card */}
|
||||
<div className="card account-list-card">
|
||||
<h4>账号列表 ({accounts?.length || 0})</h4>
|
||||
<div className="table-wrapper">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>名称</th>
|
||||
<th>状态</th>
|
||||
<th>测试网</th>
|
||||
<th>API配置</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(accounts || []).map(a => (
|
||||
<tr key={a.id}>
|
||||
<td>#{a.id}</td>
|
||||
<td>{a.name}</td>
|
||||
<td>
|
||||
<span className={`status-badge ${a.status}`}>
|
||||
{a.status === 'active' ? '启用' : '禁用'}
|
||||
</span>
|
||||
</td>
|
||||
<td>{a.use_testnet ? '是' : '否'}</td>
|
||||
<td>
|
||||
{a.has_api_key ? '✅' : '❌'} / {a.has_api_secret ? '✅' : '❌'}
|
||||
</td>
|
||||
<td>
|
||||
<div className="action-buttons">
|
||||
<button
|
||||
className="btn-sm"
|
||||
disabled={busy || a.id === 1}
|
||||
onClick={() => handleUpdateStatus(a)}
|
||||
title={a.id === 1 ? '默认账号不可禁用' : '切换状态'}
|
||||
>
|
||||
启动
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onServiceAction(acc.id, 'stop')}
|
||||
disabled={!acc.serviceStatus?.running}
|
||||
className="btn-mini stop"
|
||||
>
|
||||
停止
|
||||
{a.status === 'active' ? '禁用' : '启用'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => 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 })
|
||||
}}
|
||||
>
|
||||
✕
|
||||
密钥
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -176,37 +386,45 @@ const UserAccountGroup = ({ user, allAccounts, onServiceAction }) => {
|
|||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 关联管理区域 */}
|
||||
<div className="association-controls" style={{ marginTop: '15px', padding: '12px', backgroundColor: '#f8f9fa', borderRadius: '6px', border: '1px solid #e9ecef', display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||
<span style={{ fontSize: '13px', fontWeight: 'bold', color: '#495057' }}>新增关联:</span>
|
||||
<select
|
||||
value={linkAccountId}
|
||||
onChange={e => setLinkAccountId(e.target.value)}
|
||||
style={{ padding: '4px 8px', borderRadius: '4px', border: '1px solid #ced4da' }}
|
||||
>
|
||||
<option value="">选择账号...</option>
|
||||
{availableAccounts.map(a => (
|
||||
<option key={a.id} value={a.id}>{a.name || `Account ${a.id}`}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={linkRole}
|
||||
onChange={e => setLinkRole(e.target.value)}
|
||||
style={{ padding: '4px 8px', borderRadius: '4px', border: '1px solid #ced4da' }}
|
||||
>
|
||||
<option value="viewer">观察者 (Viewer)</option>
|
||||
<option value="trader">交易员 (Trader)</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={handleGrant}
|
||||
disabled={!linkAccountId || associating}
|
||||
className="btn-mini primary"
|
||||
style={{ padding: '4px 12px' }}
|
||||
>
|
||||
关联
|
||||
</button>
|
||||
{/* Credential Edit Modal/Overlay */}
|
||||
{credEditId && (
|
||||
<div className="modal-overlay">
|
||||
<div className="modal-content">
|
||||
<h4>更新密钥 (账号 #{credEditId})</h4>
|
||||
<div className="form-group">
|
||||
<label>API KEY (留空=不改)</label>
|
||||
<input
|
||||
type="password"
|
||||
value={credForm.api_key}
|
||||
onChange={(e) => setCredForm({ ...credForm, api_key: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>API SECRET (留空=不改)</label>
|
||||
<input
|
||||
type="password"
|
||||
value={credForm.api_secret}
|
||||
onChange={(e) => setCredForm({ ...credForm, api_secret: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group checkbox">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!credForm.use_testnet}
|
||||
onChange={(e) => setCredForm({ ...credForm, use_testnet: e.target.checked })}
|
||||
/>
|
||||
测试网
|
||||
</label>
|
||||
</div>
|
||||
<div className="modal-actions">
|
||||
<button className="btn-secondary" onClick={() => setCredEditId(null)}>取消</button>
|
||||
<button className="btn-primary" onClick={handleUpdateCreds} disabled={busy}>保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -354,6 +572,11 @@ const AdminDashboard = () => {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AccountManager
|
||||
accounts={data.allAccounts}
|
||||
onRefresh={loadData}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{/* 账号管理(超管) */}
|
||||
{isAdmin ? (
|
||||
<div className="accounts-admin-section">
|
||||
<div className="accounts-admin-header">
|
||||
<h3>账号管理(多账号)</h3>
|
||||
<div className="accounts-admin-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="system-btn"
|
||||
onClick={async () => {
|
||||
setAccountsBusy(true)
|
||||
try {
|
||||
await loadAccountsAdmin()
|
||||
setShowAccountsAdmin((v) => !v)
|
||||
} finally {
|
||||
setAccountsBusy(false)
|
||||
}
|
||||
}}
|
||||
disabled={accountsBusy}
|
||||
title="创建/禁用账号;为每个账号配置独立 API KEY/SECRET;交易/配置/统计会按账号隔离"
|
||||
>
|
||||
{showAccountsAdmin ? '收起' : '管理账号'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="system-btn"
|
||||
onClick={async () => {
|
||||
setAccountsBusy(true)
|
||||
try {
|
||||
await loadAccountsAdmin()
|
||||
notifyAccountsUpdated()
|
||||
setMessage('账号列表已刷新')
|
||||
} finally {
|
||||
setAccountsBusy(false)
|
||||
}
|
||||
}}
|
||||
disabled={accountsBusy}
|
||||
>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showAccountsAdmin ? (
|
||||
<div className="accounts-admin-body">
|
||||
<div className="accounts-admin-card">
|
||||
<div className="accounts-admin-card-title">新增账号</div>
|
||||
<div className="accounts-form">
|
||||
<label>
|
||||
名称
|
||||
<input
|
||||
type="text"
|
||||
value={newAccount.name}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, name: e.target.value })}
|
||||
placeholder="例如:user_a"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
API KEY
|
||||
<input
|
||||
type="password"
|
||||
value={newAccount.api_key}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, api_key: e.target.value })}
|
||||
placeholder="可先留空,后续再填"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
API SECRET
|
||||
<input
|
||||
type="password"
|
||||
value={newAccount.api_secret}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, api_secret: e.target.value })}
|
||||
placeholder="可先留空,后续再填"
|
||||
/>
|
||||
</label>
|
||||
<label className="accounts-inline">
|
||||
<span>测试网</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!newAccount.use_testnet}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, use_testnet: e.target.checked })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
状态
|
||||
<select
|
||||
value={newAccount.status}
|
||||
onChange={(e) => setNewAccount({ ...newAccount, status: e.target.value })}
|
||||
>
|
||||
<option value="active">启用</option>
|
||||
<option value="disabled">禁用</option>
|
||||
</select>
|
||||
</label>
|
||||
<div className="accounts-form-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="system-btn primary"
|
||||
disabled={accountsBusy || !newAccount.name.trim()}
|
||||
onClick={async () => {
|
||||
setAccountsBusy(true)
|
||||
setMessage('')
|
||||
try {
|
||||
await api.createAccount(newAccount)
|
||||
setMessage('账号已创建')
|
||||
setNewAccount({ name: '', api_key: '', api_secret: '', use_testnet: false, status: 'active' })
|
||||
await loadAccountsAdmin()
|
||||
notifyAccountsUpdated()
|
||||
} catch (e) {
|
||||
setMessage('创建账号失败: ' + (e?.message || '未知错误'))
|
||||
} finally {
|
||||
setAccountsBusy(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
创建账号
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="accounts-admin-card">
|
||||
<div className="accounts-admin-card-title">账号列表</div>
|
||||
<div className="accounts-table">
|
||||
{(accountsAdmin || []).length ? (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>名称</th>
|
||||
<th>状态</th>
|
||||
<th>测试网</th>
|
||||
<th>API KEY</th>
|
||||
<th>SECRET</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{accountsAdmin.map((a) => (
|
||||
<tr key={a.id}>
|
||||
<td>#{a.id}</td>
|
||||
<td>{a.name || '-'}</td>
|
||||
<td>
|
||||
<span className={`acct-badge ${a.status === 'active' ? 'ok' : 'off'}`}>
|
||||
{a.status === 'active' ? '启用' : '禁用'}
|
||||
</span>
|
||||
</td>
|
||||
<td>{a.use_testnet ? '是' : '否'}</td>
|
||||
<td>{a.api_key_masked || (a.has_api_key ? '已配置' : '未配置')}</td>
|
||||
<td>{a.has_api_secret ? '已配置' : '未配置'}</td>
|
||||
<td className="accounts-actions-cell">
|
||||
<button
|
||||
type="button"
|
||||
className="system-btn"
|
||||
disabled={accountsBusy || a.id === 1}
|
||||
title={a.id === 1 ? '默认账号建议保留' : '切换启用/禁用'}
|
||||
onClick={async () => {
|
||||
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' ? '禁用' : '启用'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="system-btn"
|
||||
disabled={accountsBusy}
|
||||
onClick={() => {
|
||||
setCredEditId(a.id)
|
||||
setCredForm({ api_key: '', api_secret: '', use_testnet: !!a.use_testnet })
|
||||
}}
|
||||
>
|
||||
更新密钥
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="accounts-empty">暂无账号(默认账号 #1 会自动存在)</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{credEditId ? (
|
||||
<div className="accounts-admin-card">
|
||||
<div className="accounts-admin-card-title">更新账号 #{credEditId} 的密钥</div>
|
||||
<div className="accounts-form">
|
||||
<label>
|
||||
API KEY(留空=不改)
|
||||
<input
|
||||
type="password"
|
||||
value={credForm.api_key}
|
||||
onChange={(e) => setCredForm({ ...credForm, api_key: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
API SECRET(留空=不改)
|
||||
<input
|
||||
type="password"
|
||||
value={credForm.api_secret}
|
||||
onChange={(e) => setCredForm({ ...credForm, api_secret: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="accounts-inline">
|
||||
<span>测试网</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!credForm.use_testnet}
|
||||
onChange={(e) => setCredForm({ ...credForm, use_testnet: e.target.checked })}
|
||||
/>
|
||||
</label>
|
||||
<div className="accounts-form-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="system-btn"
|
||||
disabled={accountsBusy}
|
||||
onClick={() => setCredEditId(null)}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="system-btn primary"
|
||||
disabled={accountsBusy}
|
||||
onClick={async () => {
|
||||
setAccountsBusy(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)
|
||||
await loadAccountsAdmin()
|
||||
notifyAccountsUpdated()
|
||||
} catch (e) {
|
||||
setMessage('更新密钥失败: ' + (e?.message || '未知错误'))
|
||||
} finally {
|
||||
setAccountsBusy(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* 用户提示 */}
|
||||
{!isAdmin && (
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user