From bbe27d8d1fea3a940c34a2ec8f830503109031d7 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Wed, 22 Apr 2026 12:00:30 +0800 Subject: [PATCH] 1 --- frontend/console.html | 292 +++++++++++++++++++++++++++++++++++------- 1 file changed, 247 insertions(+), 45 deletions(-) diff --git a/frontend/console.html b/frontend/console.html index 877df96..c10a6bf 100644 --- a/frontend/console.html +++ b/frontend/console.html @@ -271,6 +271,10 @@ gap: 18px; } + .layout > :only-child { + grid-column: 1 / -1; + } + .left-stack, .right-stack { display: grid; @@ -335,7 +339,7 @@ .priority-layout { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(320px, 0.84fr); + grid-template-columns: minmax(320px, 0.9fr) minmax(0, 1.1fr); gap: 18px; margin-top: 18px; } @@ -346,6 +350,94 @@ gap: 18px; } + .ops-panel-body { + min-height: 320px; + } + + .ops-pane { + display: grid; + gap: 14px; + } + + .ops-summary { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; + margin-bottom: 14px; + } + + .ops-summary-card { + appearance: none; + width: 100%; + text-align: left; + cursor: pointer; + padding: 14px 16px; + border-radius: 16px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.06); + color: var(--text); + transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease; + } + + .ops-summary-card:hover { + transform: translateY(-1px); + border-color: rgba(126, 200, 255, 0.16); + } + + .ops-summary-card.active { + border-color: rgba(126, 200, 255, 0.26); + background: rgba(126, 200, 255, 0.10); + } + + .ops-summary-card.danger { + border-color: rgba(255, 111, 97, 0.22); + background: rgba(255, 111, 97, 0.08); + } + + .ops-summary-card.warn { + border-color: rgba(255, 184, 77, 0.20); + background: rgba(255, 184, 77, 0.08); + } + + .ops-summary-kicker { + color: var(--muted); + font-size: 11px; + font-family: "IBM Plex Mono", monospace; + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 6px; + } + + .ops-summary-headline { + font-family: "IBM Plex Mono", monospace; + font-size: 18px; + color: var(--text); + margin-bottom: 6px; + } + + .ops-summary-headline.good { + color: var(--good); + } + + .ops-summary-headline.warn { + color: var(--warn); + } + + .ops-summary-headline.danger { + color: var(--danger); + } + + .ops-summary-detail { + color: var(--muted); + font-size: 12px; + line-height: 1.55; + } + + .ops-pane .analysis-log-list, + .ops-pane .halt-list { + margin-top: 2px; + } + .health-ribbon { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -1268,7 +1360,8 @@ .platform-stats, .signal-stats, .heartbeat-grid, - .health-ribbon { + .health-ribbon, + .ops-summary { grid-template-columns: 1fr; } @@ -1350,62 +1443,89 @@
正在汇总待处理事项...
- -
-
-
- -

平台执行概览

-
资金、杠杆、持仓、挂单、回撤阈值
-
-
- -
-
-
-
正在加载平台状态...
-
-
-
+
- -

分析心跳与日志

-
没有信号时,也能确认系统仍在正常扫盘
+ +

运行监控面板

+
把平台概览、分析心跳和停机熔断收进一个运行面板,避免首屏纵向堆叠
+
+
+ +
+ + + +
-
-
最近心跳-
-
最近轮次-
-
当前进度-
-
下一次运行-
+ +
+ + +
-
-
正在读取分析日志...
+ +
+
+
+ +

平台执行概览

+
资金、杠杆、持仓、挂单、回撤阈值
+
+
+
正在加载平台状态...
+
+
+ +
+
+ +

分析心跳与日志

+
没有信号时,也能确认系统仍在正常扫盘
+
+
+
最近心跳-
+
最近轮次-
+
当前进度-
+
下一次运行-
+
+
+
正在读取分析日志...
+
+
+ +
+
+ +

平台停机 / 熔断

+
风险触发后,这里应当最先看到
+
+
+
正在读取平台停机状态...
+
+ +
- -
-
-
-
- -

平台停机 / 熔断

-
风险触发后,这里应当最先看到
-
-
-
-
正在读取平台停机状态...
-
- -
-
@@ -1647,6 +1767,12 @@ }); }); }); + + document.querySelectorAll('[data-ops-target]').forEach((button) => { + button.addEventListener('click', () => { + setActiveTab('ops', button.getAttribute('data-ops-target')); + }); + }); } function setActiveTab(group, target) { @@ -1656,6 +1782,11 @@ document.querySelectorAll(`[data-tab-pane="${group}"]`).forEach((pane) => { pane.classList.toggle('active', pane.id === target); }); + if (group === 'ops') { + document.querySelectorAll('[data-ops-target]').forEach((card) => { + card.classList.toggle('active', card.getAttribute('data-ops-target') === target); + }); + } } function getActiveTabTarget(group) { @@ -1669,22 +1800,92 @@ button.classList.toggle('muted', !hasData); } + function renderOpsSummary(data) { + const container = document.getElementById('opsSummary'); + if (!container) return; + + const platforms = data.platforms || {}; + const cryptoAgent = data.crypto_agent || {}; + const monitor = cryptoAgent.analysis_monitor || {}; + const halts = cryptoAgent.platform_halts || {}; + const enabledPlatforms = ['paper', 'bitget', 'hyperliquid'].filter((key) => platforms?.[key]?.enabled !== false); + const haltedCount = countHalted(halts); + const runtimeTone = toneClassForHealth(cryptoAgent.running ? monitor.last_cycle_status || monitor.last_analysis_status : 'stopped'); + const riskTone = haltedCount > 0 ? 'danger' : ((data.management?.attention_items || []).some((item) => item.severity === 'danger' || item.severity === 'warning') ? 'warn' : 'good'); + const platformTone = haltedCount > 0 ? 'warn' : 'good'; + const platformHeadline = `${enabledPlatforms.length} 平台`; + const platformDetail = `${sumPlatformPositions(platforms)} 持仓 / ${['paper', 'bitget', 'hyperliquid'].map((key) => platforms?.[key]?.orders?.count || 0).reduce((a, b) => a + b, 0)} 挂单`; + const runtimeHeadline = monitor.last_heartbeat_at ? relativeTime(monitor.last_heartbeat_at) : '无心跳'; + const runtimeDetail = `状态 ${String(monitor.last_cycle_status || monitor.last_analysis_status || 'idle').toUpperCase()} / ${monitor.current_cycle_total ? `${monitor.current_cycle_index || 0}/${monitor.current_cycle_total}` : '待机'}`; + const riskHeadline = haltedCount > 0 ? `${haltedCount} 停机` : '无停机'; + const attentionItems = data.management?.attention_items || []; + const riskDetail = haltedCount > 0 + ? '已有平台触发停机或熔断,建议优先查看。' + : attentionItems.length > 0 + ? `待处理 ${attentionItems.length} 项,建议检查风险与执行事件。` + : '当前没有明显风险阻塞。'; + + container.innerHTML = ` + + + + `; + + container.querySelectorAll('[data-ops-target]').forEach((button) => { + button.addEventListener('click', () => { + setActiveTab('ops', button.getAttribute('data-ops-target')); + }); + }); + + setActiveTab('ops', getActiveTabTarget('ops') || 'opsPlatform'); + } + function syncTabState(data) { const recentSignals = data.signals?.latest || []; const executionEvents = data.execution_events || []; const positions = data.management?.positions || []; const orders = data.management?.orders || []; + const platformCount = ['paper', 'bitget', 'hyperliquid'] + .filter((key) => data.platforms?.[key]?.enabled !== false) + .length; 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); + const haltedCount = countHalted(data.crypto_agent?.platform_halts || {}); + const runtimeCount = (data.crypto_agent?.recent_analysis_events || []).length + (data.crypto_agent?.analysis_monitor?.last_heartbeat_at ? 1 : 0); + updateTabButton('ops', 'opsPlatform', '平台概览', platformCount, platformCount > 0); + updateTabButton('ops', 'opsRuntime', '心跳日志', runtimeCount, runtimeCount > 0); + updateTabButton('ops', 'opsRisk', '停机熔断', haltedCount, haltedCount > 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 opsCurrent = getActiveTabTarget('ops'); + const opsChoices = [ + { target: 'opsRisk', hasData: haltedCount > 0 }, + { target: 'opsRuntime', hasData: runtimeCount > 0 }, + { target: 'opsPlatform', hasData: platformCount > 0 }, + ]; + if (!opsChoices.find((item) => item.target === opsCurrent && item.hasData)) { + setActiveTab('ops', opsChoices.find((item) => item.hasData)?.target || 'opsPlatform'); + } + const workspaceCurrent = getActiveTabTarget('workspace'); const workspaceChoices = [ { target: 'workspaceCoordination', hasData: coordinationCount > 0 }, @@ -2283,6 +2484,7 @@ syncTabState(data); renderHero(data); renderHealthRibbon(data); + renderOpsSummary(data); renderPlatforms(data.platforms, data.crypto_agent?.platform_halts); renderSignalStream(data.signals?.latest || []); renderAgentSignals(data.crypto_agent, data.signals);