diff --git a/frontend/console.html b/frontend/console.html index 53d0884..877df96 100644 --- a/frontend/console.html +++ b/frontend/console.html @@ -313,6 +313,13 @@ margin-bottom: 16px; } + .panel-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + } + .panel-title { margin: 0; font-size: 18px; @@ -653,6 +660,26 @@ background: rgba(126, 200, 255, 0.12); } + .workspace-tab.muted { + color: rgba(142, 166, 188, 0.72); + border-color: rgba(255,255,255,0.05); + } + + .tab-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + margin-left: 6px; + padding: 0 6px; + border-radius: 999px; + background: rgba(255,255,255,0.08); + color: inherit; + font-size: 10px; + line-height: 1; + } + .tab-pane { display: none; } @@ -667,7 +694,7 @@ } .workspace-stream { - min-height: 240px; + min-height: 0; } .event-list { @@ -702,6 +729,27 @@ background: rgba(126, 200, 255, 0.10); } + .ghost-btn { + appearance: none; + border: 1px solid rgba(255,255,255,0.08); + background: rgba(255,255,255,0.03); + color: var(--muted); + cursor: pointer; + border-radius: 999px; + padding: 8px 12px; + font-size: 11px; + font-family: "IBM Plex Mono", monospace; + text-transform: uppercase; + letter-spacing: 0.05em; + transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease; + } + + .ghost-btn.active { + color: var(--cold); + border-color: rgba(126, 200, 255, 0.24); + background: rgba(126, 200, 255, 0.1); + } + .event-item { display: grid; grid-template-columns: 120px 110px 1fr; @@ -1163,6 +1211,25 @@ border-color: rgba(255, 111, 97, 0.2); } + .empty-box.compact { + padding: 14px 16px; + text-align: left; + border-radius: 14px; + } + + .empty-box.compact strong { + display: block; + color: var(--text); + font-size: 13px; + margin-bottom: 4px; + } + + .empty-detail { + color: var(--muted); + font-size: 12px; + line-height: 1.55; + } + @media (max-width: 1240px) { .hero, .layout, @@ -1291,6 +1358,9 @@

平台执行概览

资金、杠杆、持仓、挂单、回撤阈值
+
+ +
正在加载平台状态...
@@ -1451,6 +1521,9 @@ let timer = null; let currentEventFilter = 'all'; let cachedExecutionEvents = []; + let cachedConsoleData = null; + let revealSensitiveData = false; + const SENSITIVE_VISIBILITY_KEY = 'console_sensitive_visible_v2'; function formatNumber(value, digits = 2) { const num = Number(value || 0); @@ -1521,6 +1594,48 @@ el.innerHTML = `
${message}
`; } + function compactEmpty(title, detail = '') { + return ` +
+ ${title} + ${detail ? `
${detail}
` : ''} +
+ `; + } + + function loadSensitivePreference() { + try { + revealSensitiveData = window.localStorage.getItem(SENSITIVE_VISIBILITY_KEY) === '1'; + } catch { + revealSensitiveData = false; + } + renderSensitiveToggle(); + } + + function renderSensitiveToggle() { + const button = document.getElementById('toggleSensitiveBtn'); + if (!button) return; + button.textContent = `敏感数据: ${revealSensitiveData ? '显示' : '隐藏'}`; + button.classList.toggle('active', revealSensitiveData); + } + + function toggleSensitiveData() { + revealSensitiveData = !revealSensitiveData; + try { + window.localStorage.setItem(SENSITIVE_VISIBILITY_KEY, revealSensitiveData ? '1' : '0'); + } catch { + // ignore storage errors + } + renderSensitiveToggle(); + if (cachedConsoleData) { + renderPlatforms(cachedConsoleData.platforms, cachedConsoleData.crypto_agent?.platform_halts); + } + } + + function formatSensitiveMoney(value) { + return revealSensitiveData ? formatMoney(value) : '$••••'; + } + function initTabs() { document.querySelectorAll('[data-tab-group]').forEach((groupEl) => { const group = groupEl.getAttribute('data-tab-group'); @@ -1528,17 +1643,68 @@ buttons.forEach((button) => { button.addEventListener('click', () => { const target = button.getAttribute('data-target'); - buttons.forEach((item) => { - item.classList.toggle('active', item === button); - }); - document.querySelectorAll(`[data-tab-pane="${group}"]`).forEach((pane) => { - pane.classList.toggle('active', pane.id === target); - }); + setActiveTab(group, target); }); }); }); } + function setActiveTab(group, target) { + document.querySelectorAll(`[data-tab="${group}"]`).forEach((button) => { + button.classList.toggle('active', button.getAttribute('data-target') === target); + }); + document.querySelectorAll(`[data-tab-pane="${group}"]`).forEach((pane) => { + pane.classList.toggle('active', pane.id === target); + }); + } + + function getActiveTabTarget(group) { + return document.querySelector(`[data-tab="${group}"].active`)?.getAttribute('data-target') || null; + } + + function updateTabButton(group, target, label, count, hasData) { + const button = document.querySelector(`[data-tab="${group}"][data-target="${target}"]`); + if (!button) return; + button.innerHTML = `${label}${count}`; + button.classList.toggle('muted', !hasData); + } + + function syncTabState(data) { + const recentSignals = data.signals?.latest || []; + const executionEvents = data.execution_events || []; + const positions = data.management?.positions || []; + const orders = data.management?.orders || []; + const lastSignals = Object.keys(data.crypto_agent?.last_signals || {}).length; + const previews = Object.keys(data.crypto_agent?.last_execution_preview || {}).length; + const statsTotal = Number(data.signals?.stats_7d?.total || 0); + const coordinationCount = lastSignals + previews + (statsTotal > 0 ? 1 : 0); + + updateTabButton('workspace', 'workspaceCoordination', '协同', coordinationCount, coordinationCount > 0); + updateTabButton('workspace', 'workspaceSignals', '信号流', recentSignals.length, recentSignals.length > 0); + updateTabButton('workspace', 'workspaceExecution', '执行流', executionEvents.length, executionEvents.length > 0); + updateTabButton('asset', 'assetPositions', '持仓', positions.length, positions.length > 0); + updateTabButton('asset', 'assetOrders', '挂单', orders.length, orders.length > 0); + + const workspaceCurrent = getActiveTabTarget('workspace'); + const workspaceChoices = [ + { target: 'workspaceCoordination', hasData: coordinationCount > 0 }, + { target: 'workspaceExecution', hasData: executionEvents.length > 0 }, + { target: 'workspaceSignals', hasData: recentSignals.length > 0 }, + ]; + if (!workspaceChoices.find((item) => item.target === workspaceCurrent && item.hasData)) { + setActiveTab('workspace', workspaceChoices.find((item) => item.hasData)?.target || 'workspaceCoordination'); + } + + const assetCurrent = getActiveTabTarget('asset'); + const assetChoices = [ + { target: 'assetPositions', hasData: positions.length > 0 }, + { target: 'assetOrders', hasData: orders.length > 0 }, + ]; + if (!assetChoices.find((item) => item.target === assetCurrent && item.hasData)) { + setActiveTab('asset', assetChoices.find((item) => item.hasData)?.target || 'assetPositions'); + } + } + function toneClassForHealth(status) { if (['error', 'failed', 'stopped'].includes(String(status || '').toLowerCase())) return 'danger'; if (['warning', 'halted', 'idle'].includes(String(status || '').toLowerCase())) return 'warn'; @@ -1677,11 +1843,11 @@
权益 - ${formatMoney(account.current_balance || account.account_value)} + ${formatSensitiveMoney(account.current_balance || account.account_value)}
可用 - ${formatMoney(account.available || account.available_balance)} + ${formatSensitiveMoney(account.available || account.available_balance)}
持仓 @@ -1714,7 +1880,7 @@ function renderSignalStream(signals) { const container = document.getElementById('signalStream'); if (!signals || signals.length === 0) { - container.innerHTML = '
最近没有可展示信号
'; + container.innerHTML = compactEmpty('最近没有可展示信号', '等待新的分析结果写入,运行状态可继续看上方心跳。'); return; } @@ -1755,7 +1921,7 @@ `); if (entries.length === 0) { - cards.push('
Crypto Agent 暂无最近信号缓存
'); + cards.push(compactEmpty('Crypto Agent 暂无最近信号缓存', '当前没有缓存到最近信号,可切到信号流或执行流继续查看。')); } else { entries.slice(0, 5).forEach(([symbol, sig]) => { cards.push(` @@ -1809,7 +1975,7 @@ `; if (!analysisEvents || analysisEvents.length === 0) { - logList.innerHTML = '
最近还没有分析日志
'; + logList.innerHTML = compactEmpty('最近还没有分析日志', '等待下一轮分析或新的运行事件写入。'); return; } @@ -1847,7 +2013,7 @@ const entries = Object.entries(previewMap || {}); if (entries.length === 0) { - container.innerHTML = '
暂无最近决策预览
'; + container.innerHTML = compactEmpty('暂无最近决策预览', '还没有形成最近一轮执行预览,通常意味着还没出现可执行信号。'); return; } @@ -1927,7 +2093,7 @@ cachedExecutionEvents = Array.isArray(events) ? events : []; const filtered = cachedExecutionEvents.filter((event) => currentEventFilter === 'all' || event.status === currentEventFilter); if (!filtered || filtered.length === 0) { - container.innerHTML = '
最近还没有执行事件
'; + container.innerHTML = compactEmpty('最近没有匹配的执行事件', currentEventFilter === 'all' ? '当前没有新的执行结果或异常事件。' : `筛选条件为 ${currentEventFilter.toUpperCase()},当前暂无匹配记录。`); return; } @@ -1956,7 +2122,7 @@ function renderAttentionItems(items) { const container = document.getElementById('attentionList'); if (!items || items.length === 0) { - container.innerHTML = '
当前没有需要人工处理的事项
'; + container.innerHTML = compactEmpty('当前没有需要人工处理的事项', '系统暂无明显风险、停机或待干预问题。'); return; } @@ -1982,7 +2148,7 @@ toolbar.innerHTML = `${platformCounts}total: ${total.length}`; if (!total.length) { - container.innerHTML = '
当前没有跨平台持仓
'; + container.innerHTML = compactEmpty('当前没有跨平台持仓', '没有活动持仓时,这里会保持紧凑,不再占用大块留白。'); return; } @@ -2033,7 +2199,7 @@ `; if (!total.length) { - container.innerHTML = '
当前没有跨平台挂单
'; + container.innerHTML = compactEmpty('当前没有跨平台挂单', '没有入场单或保护单时,这里会保持紧凑展示。'); return; } @@ -2113,6 +2279,8 @@ } const data = result.data || {}; + cachedConsoleData = data; + syncTabState(data); renderHero(data); renderHealthRibbon(data); renderPlatforms(data.platforms, data.crypto_agent?.platform_halts); @@ -2153,6 +2321,7 @@ autoRefresh = !autoRefresh; applyAutoRefreshState(); }); + document.getElementById('toggleSensitiveBtn').addEventListener('click', toggleSensitiveData); document.getElementById('eventFilters').querySelectorAll('[data-filter]').forEach((button) => { button.addEventListener('click', () => { currentEventFilter = button.getAttribute('data-filter'); @@ -2163,6 +2332,7 @@ }); }); + loadSensitivePreference(); initTabs(); applyAutoRefreshState(); loadConsole();