1
This commit is contained in:
parent
e98b1c3c9c
commit
75c7fd4f0c
@ -11,6 +11,8 @@ from app.crypto_agent.crypto_agent import get_crypto_agent
|
||||
from app.services.signal_database_service import get_signal_db_service
|
||||
from app.services.paper_trading_service import get_paper_trading_service
|
||||
from app.services.bitget_live_trading_service import get_all_bitget_live_services, get_bitget_live_service
|
||||
from app.services.price_monitor_service import get_price_monitor_service
|
||||
from app.services.runtime_status_service import get_runtime_status
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@ -429,6 +431,11 @@ async def get_console_snapshot():
|
||||
if (_parse_signal_timestamp(signal.get("created_at")) or datetime.min) >= recent_cutoff
|
||||
)
|
||||
|
||||
price_monitor = get_price_monitor_service()
|
||||
configured_symbols = [symbol.strip().upper() for symbol in (get_settings().crypto_symbols or "").split(",") if symbol.strip()]
|
||||
for symbol in configured_symbols:
|
||||
price_monitor.subscribe_symbol(symbol)
|
||||
|
||||
paper_position_items = [
|
||||
_normalize_platform_position("paper", pos)
|
||||
for pos in paper_service.get_open_positions()[:12]
|
||||
@ -517,6 +524,16 @@ async def get_console_snapshot():
|
||||
"recent_30m_count": recent_signal_count,
|
||||
},
|
||||
"platforms": platforms_payload,
|
||||
"monitoring": {
|
||||
"price_monitor": {
|
||||
"running": price_monitor.is_running(),
|
||||
"mode": "websocket" if getattr(price_monitor, "_use_websocket", False) else "polling",
|
||||
"subscribed_symbols": price_monitor.get_subscribed_symbols(),
|
||||
"latest_prices": price_monitor.get_all_prices(),
|
||||
"checked_at": now.isoformat(),
|
||||
},
|
||||
"execution_loop": get_runtime_status("price_monitor_loop"),
|
||||
},
|
||||
"management": {
|
||||
"positions": unified_positions[:18],
|
||||
"orders": unified_orders[:24],
|
||||
|
||||
@ -5,7 +5,7 @@ import asyncio
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from contextlib import asynccontextmanager
|
||||
from app.config import get_settings
|
||||
from app.utils.logger import logger
|
||||
@ -485,7 +485,7 @@ if os.path.exists(frontend_path):
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""根路径,返回主应用页面"""
|
||||
index_path = os.path.join(frontend_path, "trading.html")
|
||||
index_path = os.path.join(frontend_path, "console.html")
|
||||
if os.path.exists(index_path):
|
||||
return FileResponse(index_path)
|
||||
return {"message": "页面不存在"}
|
||||
@ -493,7 +493,7 @@ async def root():
|
||||
@app.get("/app")
|
||||
async def app_page():
|
||||
"""主应用页面"""
|
||||
index_path = os.path.join(frontend_path, "trading.html")
|
||||
index_path = os.path.join(frontend_path, "console.html")
|
||||
if os.path.exists(index_path):
|
||||
return FileResponse(index_path)
|
||||
return {"message": "页面不存在"}
|
||||
@ -505,19 +505,13 @@ async def health_check():
|
||||
|
||||
@app.get("/trading")
|
||||
async def trading_page():
|
||||
"""交易页面"""
|
||||
page_path = os.path.join(frontend_path, "trading.html")
|
||||
if os.path.exists(page_path):
|
||||
return FileResponse(page_path)
|
||||
return {"message": "页面不存在"}
|
||||
"""交易页面兼容入口,统一跳转到总控台"""
|
||||
return RedirectResponse(url="/console", status_code=307)
|
||||
|
||||
@app.get("/bitget-trading")
|
||||
async def bitget_trading_page():
|
||||
"""Bitget 交易页面兼容入口,统一跳转到当前 trading 页面"""
|
||||
page_path = os.path.join(frontend_path, "trading.html")
|
||||
if os.path.exists(page_path):
|
||||
return FileResponse(page_path)
|
||||
return {"message": "页面不存在"}
|
||||
"""Bitget 交易页面兼容入口,统一跳转到总控台"""
|
||||
return RedirectResponse(url="/console", status_code=307)
|
||||
|
||||
@app.get("/signals")
|
||||
async def signals_page():
|
||||
|
||||
@ -58,6 +58,167 @@
|
||||
padding: 24px 0 40px;
|
||||
}
|
||||
|
||||
.console-hub {
|
||||
display: grid;
|
||||
grid-template-columns: 240px minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.console-sidebar {
|
||||
position: sticky;
|
||||
top: 18px;
|
||||
padding: 16px;
|
||||
border-radius: 22px;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.console-sidebar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
border-radius: 22px;
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.04), transparent 24%, transparent 76%, rgba(126, 200, 255, 0.04));
|
||||
}
|
||||
|
||||
.sidebar-label {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
margin-bottom: 12px;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.sidebar-link {
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
background: rgba(255,255,255,0.03);
|
||||
color: var(--text);
|
||||
transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar-link:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: rgba(126, 200, 255, 0.18);
|
||||
}
|
||||
|
||||
.sidebar-link.active {
|
||||
border-color: rgba(126, 200, 255, 0.28);
|
||||
background: rgba(126, 200, 255, 0.10);
|
||||
}
|
||||
|
||||
.sidebar-link-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.sidebar-link-sub {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.sidebar-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
height: 20px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: var(--cold);
|
||||
font-size: 10px;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
}
|
||||
|
||||
.hub-content {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.hub-pane {
|
||||
display: none;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.hub-pane.active {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.pane-grid {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.pane-grid.two-col {
|
||||
grid-template-columns: minmax(0, 1.08fr) minmax(320px, 0.92fr);
|
||||
}
|
||||
|
||||
.pane-grid.equal {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.compact-panel-body {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.monitor-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.monitor-card {
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
|
||||
.monitor-card .title {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
margin-bottom: 8px;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.monitor-card .main {
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
font-size: 16px;
|
||||
color: var(--text);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.monitor-card .meta {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.4fr) minmax(360px, 0.9fr);
|
||||
@ -2211,6 +2372,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 1240px) {
|
||||
.console-hub,
|
||||
.hero,
|
||||
.layout,
|
||||
.priority-layout,
|
||||
@ -2219,11 +2381,23 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.console-sidebar {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.platform-grid,
|
||||
.signal-grid,
|
||||
.ops-grid,
|
||||
.coord-grid,
|
||||
.position-card-grid {
|
||||
.position-card-grid,
|
||||
.pane-grid.two-col,
|
||||
.pane-grid.equal,
|
||||
.monitor-strip,
|
||||
.runtime-summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@ -2251,6 +2425,10 @@
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hero-main,
|
||||
.hero-side,
|
||||
.panel,
|
||||
@ -2294,7 +2472,7 @@
|
||||
<div class="eyebrow">System Console</div>
|
||||
<h1 class="hero-title">交易系统 <strong>总控台</strong></h1>
|
||||
<p class="hero-subtitle">
|
||||
统一观察信号流、执行层、三端账户风险和平台熔断状态。
|
||||
统一观察信号流、执行层、多账户风险和平台熔断状态。
|
||||
这个页面的目标不是展示“历史”,而是让你在一屏内判断系统现在是不是健康、哪里堵住了、哪里需要人工接管。
|
||||
</p>
|
||||
<div class="hero-metrics" id="heroMetrics">
|
||||
@ -2327,8 +2505,34 @@
|
||||
|
||||
<div id="feedback"></div>
|
||||
|
||||
<section class="layout">
|
||||
<div class="left-stack">
|
||||
<section class="console-hub">
|
||||
<aside class="console-sidebar">
|
||||
<div class="sidebar-label">Navigation</div>
|
||||
<div class="sidebar-nav" id="sidebarNav">
|
||||
<button class="sidebar-link active" data-hub-target="hubOverview">
|
||||
<div class="sidebar-link-title">总览 <span class="sidebar-badge" id="sidebarBadgeOverview">-</span></div>
|
||||
<div class="sidebar-link-sub">系统健康、优先级、执行概览</div>
|
||||
</button>
|
||||
<button class="sidebar-link" data-hub-target="hubExecution">
|
||||
<div class="sidebar-link-title">执行资产 <span class="sidebar-badge" id="sidebarBadgeExecution">-</span></div>
|
||||
<div class="sidebar-link-sub">平台、持仓、挂单、自动交易控制</div>
|
||||
</button>
|
||||
<button class="sidebar-link" data-hub-target="hubSignals">
|
||||
<div class="sidebar-link-title">信号决策 <span class="sidebar-badge" id="sidebarBadgeSignals">-</span></div>
|
||||
<div class="sidebar-link-sub">生命周期、预览、信号缓存</div>
|
||||
</button>
|
||||
<button class="sidebar-link" data-hub-target="hubRuntime">
|
||||
<div class="sidebar-link-title">运行监控 <span class="sidebar-badge" id="sidebarBadgeRuntime">-</span></div>
|
||||
<div class="sidebar-link-sub">心跳、调度、guardian、事件触发</div>
|
||||
</button>
|
||||
<button class="sidebar-link" data-hub-target="hubLogs">
|
||||
<div class="sidebar-link-title">日志事件 <span class="sidebar-badge" id="sidebarBadgeLogs">-</span></div>
|
||||
<div class="sidebar-link-sub">执行事件、分析日志、阻塞归因</div>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="hub-content">
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<div>
|
||||
@ -2336,6 +2540,9 @@
|
||||
<h2 class="panel-title" style="margin-top: 12px;">系统健康总览</h2>
|
||||
<div class="panel-sub">先判断系统活着没有,再判断风险和堵点</div>
|
||||
</div>
|
||||
<div class="panel-actions">
|
||||
<button class="ghost-btn" id="toggleSensitiveBtn">敏感数据: 隐藏</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="health-ribbon" id="healthRibbon">
|
||||
<div class="health-card"><div class="kicker">分析状态</div><div class="headline">-</div></div>
|
||||
@ -2351,136 +2558,195 @@
|
||||
<div class="alert-strip" id="alertStrip"></div>
|
||||
</section>
|
||||
|
||||
<section class="hub-pane active" id="hubOverview">
|
||||
<div class="pane-grid">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Command Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">指挥台视图</h2>
|
||||
<div class="panel-sub">先看系统机会与风险,再下钻到运行细节和日志</div>
|
||||
</div>
|
||||
<div class="panel-actions">
|
||||
<button class="ghost-btn" id="toggleSensitiveBtn">敏感数据: 隐藏</button>
|
||||
<div class="workspace-tabs" data-tab-group="command">
|
||||
<button class="workspace-tab active" data-tab="command" data-target="commandOverview">总览</button>
|
||||
<button class="workspace-tab" data-tab="command" data-target="commandRisk">风险</button>
|
||||
<button class="workspace-tab" data-tab="command" data-target="commandRuntime">运行</button>
|
||||
<button class="workspace-tab" data-tab="command" data-target="commandLogs">日志</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane active" data-tab-pane="command" id="commandOverview">
|
||||
<div class="overview-grid">
|
||||
<div class="overview-main">
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Priority Layer</div>
|
||||
<h3 class="block-title">当前最重要的问题</h3>
|
||||
<div class="block-sub">先回答现在最需要你看的是什么,以及你该做什么</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">当前最重要的问题</h2>
|
||||
<div class="panel-sub">先回答你现在最该关注什么,再决定是否继续下钻</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="compact-panel-body">
|
||||
<div class="top-priority-grid" id="topPriority">
|
||||
<div class="loading">正在整理当前优先级...</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Ops Layer</div>
|
||||
<h3 class="block-title">系统运营指标</h3>
|
||||
<div class="block-sub">看系统过去一段时间是否真的有分析、信号和执行产出</div>
|
||||
</div>
|
||||
<div class="ops-kpi-grid" id="opsKpis">
|
||||
<div class="loading">正在汇总运营指标...</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Lifecycle Layer</div>
|
||||
<h3 class="block-title">信号生命周期</h3>
|
||||
<div class="block-sub">把信号、执行结果、挂单和持仓串起来,不再分散查看</div>
|
||||
</div>
|
||||
<div class="lifecycle-list" id="signalLifecycle">
|
||||
<div class="loading">正在整理信号生命周期...</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Decision Layer</div>
|
||||
<h3 class="block-title">最近决策预览</h3>
|
||||
<div class="block-sub">优先看系统准备做什么,而不是先看历史事件</div>
|
||||
|
||||
<div class="pane-grid two-col">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Execution Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">平台执行概览</h2>
|
||||
<div class="panel-sub">统一看平台权益、仓位、挂单、停机与自动交易状态</div>
|
||||
</div>
|
||||
<div class="signal-grid" id="decisionPreview">
|
||||
<div class="loading">正在读取决策预览...</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Platform Layer</div>
|
||||
<h3 class="block-title">平台执行概览</h3>
|
||||
<div class="block-sub">资金、杠杆、持仓、挂单、回撤阈值</div>
|
||||
</div>
|
||||
<div class="platform-grid" id="platformGrid">
|
||||
<div class="loading">正在加载平台状态...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Attention Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">待处理清单</h2>
|
||||
<div class="panel-sub">保留给人工干预和快速核对的辅助视图</div>
|
||||
</div>
|
||||
<div class="overview-side">
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Risk Layer</div>
|
||||
<h3 class="block-title">待处理清单</h3>
|
||||
<div class="block-sub">首屏的辅助核对区,完整优先级已提升到左侧顶部</div>
|
||||
</div>
|
||||
<div class="attention-list" id="attentionList">
|
||||
<div class="loading">正在汇总待处理事项...</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Market Layer</div>
|
||||
<h3 class="block-title">最近信号流</h3>
|
||||
<div class="block-sub">只保留紧凑摘要,不让历史列表抢占主视图</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hub-pane" id="hubExecution">
|
||||
<div class="pane-grid">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Asset Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">执行与资产</h2>
|
||||
<div class="panel-sub">以后不再看 trading 页面,这里承接持仓、挂单和执行状态</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="monitor-strip" id="executionMonitorStrip">
|
||||
<div class="monitor-card">
|
||||
<div class="title">价格链路</div>
|
||||
<div class="main">等待刷新</div>
|
||||
<div class="meta">状态加载中</div>
|
||||
</div>
|
||||
<div class="monitor-card">
|
||||
<div class="title">模拟盘执行链路</div>
|
||||
<div class="main">等待刷新</div>
|
||||
<div class="meta">状态加载中</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel workspace-panel unified-section">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Position Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">统一持仓视图</h2>
|
||||
<div class="panel-sub">跨平台持仓合并展示,优先看风险、盈亏和保护完整度</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-toolbar" id="positionsToolbar"></div>
|
||||
<div class="unified-table" id="positionsTable">
|
||||
<div class="loading">正在整理跨平台持仓...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel workspace-panel unified-section">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Order Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">统一挂单视图</h2>
|
||||
<div class="panel-sub">入场单、保护单、资金占用和老化状态集中查看</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-toolbar" id="ordersToolbar"></div>
|
||||
<div class="unified-table" id="ordersTable">
|
||||
<div class="loading">正在整理跨平台挂单...</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hub-pane" id="hubSignals">
|
||||
<div class="pane-grid">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Lifecycle Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">信号与决策</h2>
|
||||
<div class="panel-sub">把最近信号、生命周期、预览和缓存收敛到一个入口</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lifecycle-list" id="signalLifecycle">
|
||||
<div class="loading">正在整理信号生命周期...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="pane-grid two-col">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Decision Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">最近决策预览</h2>
|
||||
<div class="panel-sub">看系统准备做什么,以及不同执行目标如何响应</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="signal-grid" id="decisionPreview">
|
||||
<div class="loading">正在读取决策预览...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Signal Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">最近信号流</h2>
|
||||
<div class="panel-sub">压缩展示最新机会,但保留关键信号细节</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stream-list workspace-stream scroll-panel" id="signalStream">
|
||||
<div class="loading">正在读取信号...</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" data-tab-pane="command" id="commandRisk">
|
||||
<div class="focus-grid">
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Risk Layer</div>
|
||||
<h3 class="block-title">平台停机 / 熔断</h3>
|
||||
<div class="block-sub">风险触发后,这里应当最先看到</div>
|
||||
<div class="pane-grid equal">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Agent Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">Crypto Agent 状态</h2>
|
||||
<div class="panel-sub">最近信号缓存、统计和辅助协同信息</div>
|
||||
</div>
|
||||
<div class="halt-list" id="haltList">
|
||||
<div class="loading">正在读取平台停机状态...</div>
|
||||
</div>
|
||||
<div class="signal-grid" id="agentSignals">
|
||||
<div class="loading">正在读取 Agent 状态...</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Blocked Layer</div>
|
||||
<h3 class="block-title">最近阻塞原因</h3>
|
||||
<div class="block-sub">未落单、风险过滤、执行限制统一归因</div>
|
||||
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Mirror Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">补充视角</h2>
|
||||
<div class="panel-sub">保留对照区,避免主区过度堆叠</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="signal-grid" id="coordinationMirror">
|
||||
<div class="empty-box compact">
|
||||
<strong>主视图已集中到本菜单</strong>
|
||||
<div class="empty-detail">后续如需要补充“信号转执行”专题卡片,可以继续在这里加,不影响总览区。</div>
|
||||
</div>
|
||||
<div class="blocked-list" id="blockedSummaryList">
|
||||
<div class="loading">正在读取未落单汇总...</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="tab-pane" data-tab-pane="command" id="commandRuntime">
|
||||
<div class="focus-grid">
|
||||
<div class="overview-main">
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
<section class="hub-pane" id="hubRuntime">
|
||||
<div class="pane-grid two-col">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Runtime Layer</div>
|
||||
<h3 class="block-title">分析心跳</h3>
|
||||
<div class="block-sub">没有信号时,也能确认系统仍在正常扫盘</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">分析心跳与调度</h2>
|
||||
<div class="panel-sub">确认系统持续在分析,而不是单纯没有信号</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="heartbeat-grid" id="analysisHeartbeat">
|
||||
<div class="heartbeat-card"><span class="label">最近心跳</span><span class="value">-</span></div>
|
||||
@ -2499,28 +2765,48 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="overview-side">
|
||||
<section class="coord-block focus-panel">
|
||||
<div class="block-head">
|
||||
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Guardian Layer</div>
|
||||
<h3 class="block-title">执行监管目标</h3>
|
||||
<div class="block-sub">监管 target、保护单补救、持仓管理能力</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">执行监管目标</h2>
|
||||
<div class="panel-sub">监管 target、保护单补救和持仓管理能力集中展示</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="analysis-log-list" id="guardianTargetList">
|
||||
<div class="loading">正在读取执行监管目标...</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="tab-pane" data-tab-pane="command" id="commandLogs">
|
||||
<section class="hub-pane" id="hubLogs">
|
||||
<div class="pane-grid">
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Risk Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">风险与阻塞</h2>
|
||||
<div class="panel-sub">停机、熔断、执行阻塞统一在这里看,不再散落在各处</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pane-grid equal">
|
||||
<div class="halt-list" id="haltList">
|
||||
<div class="loading">正在读取平台停机状态...</div>
|
||||
</div>
|
||||
<div class="blocked-list" id="blockedSummaryList">
|
||||
<div class="loading">正在读取未落单汇总...</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head log-head">
|
||||
<div>
|
||||
<div class="section-label">Log Layer</div>
|
||||
<h3 class="block-title">运行与执行日志</h3>
|
||||
<div class="block-sub">日志放到最后一级,按需查看,不再占用首屏决策空间</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">运行与执行日志</h2>
|
||||
<div class="panel-sub">日志保留在独立菜单,按需查看</div>
|
||||
</div>
|
||||
<div class="workspace-tabs" data-tab-group="logs">
|
||||
<button class="workspace-tab active" data-tab="logs" data-target="logsExecution">执行事件</button>
|
||||
@ -2544,102 +2830,9 @@
|
||||
<div class="loading">正在读取分析日志...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel workspace-panel">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Drilldown Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">补充工作区</h2>
|
||||
<div class="panel-sub">首屏用于判断,补充区用于核对账户、信号缓存和明细状态</div>
|
||||
</div>
|
||||
<div class="workspace-tabs" data-tab-group="workspace">
|
||||
<button class="workspace-tab active" data-tab="workspace" data-target="workspaceCoordination">账户协同</button>
|
||||
<button class="workspace-tab" data-tab="workspace" data-target="workspaceSignals">信号缓存</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane active" data-tab-pane="workspace" id="workspaceCoordination">
|
||||
<div class="coord-grid">
|
||||
<div class="coord-block">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Agent Layer</div>
|
||||
<h3 class="block-title">Crypto Agent 状态</h3>
|
||||
<div class="block-sub">最近信号、平台停机、执行层状态</div>
|
||||
</div>
|
||||
<div class="signal-grid" id="agentSignals">
|
||||
<div class="loading">正在读取 Agent 状态...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="coord-block">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Decision Layer</div>
|
||||
<h3 class="block-title">Agent / 账户协同</h3>
|
||||
<div class="block-sub">保留辅助视角,用于对照信号缓存与系统工作状态</div>
|
||||
</div>
|
||||
<div class="signal-grid" id="coordinationMirror">
|
||||
<div class="empty-box compact">
|
||||
<strong>主视图已迁移到指挥台</strong>
|
||||
<div class="empty-detail">最近决策预览已放到上方“总览”主标签,这里仅保留补充信息,避免重复占屏。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" data-tab-pane="workspace" id="workspaceSignals">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Market Layer</div>
|
||||
<h3 class="block-title">最近信号缓存</h3>
|
||||
<div class="block-sub">用于核对 Agent 最近缓存的信号,不再重复展示完整主信号流</div>
|
||||
</div>
|
||||
<div class="signal-grid" id="signalMirror">
|
||||
<div class="empty-box compact">
|
||||
<strong>主信号流已迁移到指挥台</strong>
|
||||
<div class="empty-detail">最近信号流已放到上方“总览”主标签,这里保留为信号缓存核对区。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel workspace-panel unified-section">
|
||||
<div class="workspace-head">
|
||||
<div>
|
||||
<div class="section-label">Asset Layer</div>
|
||||
<h2 class="panel-title" style="margin-top: 12px;">资产与挂单</h2>
|
||||
<div class="panel-sub">统一持仓和统一挂单改为切换查看,减少表格同时占屏</div>
|
||||
</div>
|
||||
<div class="workspace-tabs" data-tab-group="asset">
|
||||
<button class="workspace-tab active" data-tab="asset" data-target="assetPositions">持仓</button>
|
||||
<button class="workspace-tab" data-tab="asset" data-target="assetOrders">挂单</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane active" data-tab-pane="asset" id="assetPositions">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Position Layer</div>
|
||||
<h3 class="block-title">统一持仓视图</h3>
|
||||
<div class="block-sub">三端持仓合并,优先看风险与盈亏</div>
|
||||
</div>
|
||||
<div class="table-toolbar" id="positionsToolbar"></div>
|
||||
<div class="unified-table" id="positionsTable">
|
||||
<div class="loading">正在整理跨平台持仓...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" data-tab-pane="asset" id="assetOrders">
|
||||
<div class="block-head">
|
||||
<div class="section-label">Order Layer</div>
|
||||
<h3 class="block-title">统一挂单视图</h3>
|
||||
<div class="block-sub">入场单、保护单、资金占用一屏观察</div>
|
||||
</div>
|
||||
<div class="table-toolbar" id="ordersToolbar"></div>
|
||||
<div class="unified-table" id="ordersTable">
|
||||
<div class="loading">正在整理跨平台挂单...</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@ -2652,6 +2845,7 @@
|
||||
let cachedConsoleData = null;
|
||||
let revealSensitiveData = false;
|
||||
const SENSITIVE_VISIBILITY_KEY = 'console_sensitive_visible_v2';
|
||||
const HUB_PREFERENCE_KEY = 'console_hub_active_v1';
|
||||
|
||||
function formatNumber(value, digits = 2) {
|
||||
const num = Number(value || 0);
|
||||
@ -3011,6 +3205,42 @@
|
||||
return revealSensitiveData ? formatMoney(value) : '$••••';
|
||||
}
|
||||
|
||||
function initHubNavigation() {
|
||||
document.querySelectorAll('[data-hub-target]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
setActiveHub(button.getAttribute('data-hub-target'));
|
||||
});
|
||||
});
|
||||
|
||||
let preferredHub = 'hubOverview';
|
||||
try {
|
||||
preferredHub = window.localStorage.getItem(HUB_PREFERENCE_KEY) || 'hubOverview';
|
||||
} catch {
|
||||
preferredHub = 'hubOverview';
|
||||
}
|
||||
setActiveHub(preferredHub);
|
||||
}
|
||||
|
||||
function setActiveHub(target) {
|
||||
document.querySelectorAll('[data-hub-target]').forEach((button) => {
|
||||
button.classList.toggle('active', button.getAttribute('data-hub-target') === target);
|
||||
});
|
||||
document.querySelectorAll('.hub-pane').forEach((pane) => {
|
||||
pane.classList.toggle('active', pane.id === target);
|
||||
});
|
||||
try {
|
||||
window.localStorage.setItem(HUB_PREFERENCE_KEY, target);
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
function updateSidebarBadge(id, value) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.textContent = value;
|
||||
}
|
||||
|
||||
function initTabs() {
|
||||
document.querySelectorAll('[data-tab-group]').forEach((groupEl) => {
|
||||
const group = groupEl.getAttribute('data-tab-group');
|
||||
@ -3022,12 +3252,6 @@
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-ops-target]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
setActiveTab('ops', button.getAttribute('data-ops-target'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setActiveTab(group, target) {
|
||||
@ -3037,11 +3261,6 @@
|
||||
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) {
|
||||
@ -3055,60 +3274,33 @@
|
||||
button.classList.toggle('muted', !hasData);
|
||||
}
|
||||
|
||||
function renderOpsSummary(data) {
|
||||
const container = document.getElementById('opsSummary');
|
||||
function renderExecutionMonitor(data) {
|
||||
const container = document.getElementById('executionMonitorStrip');
|
||||
if (!container) return;
|
||||
|
||||
const platforms = data.platforms || {};
|
||||
const cryptoAgent = data.crypto_agent || {};
|
||||
const monitor = cryptoAgent.analysis_monitor || {};
|
||||
const halts = cryptoAgent.platform_halts || {};
|
||||
const executionControls = cryptoAgent.target_execution_controls || {};
|
||||
const enabledPlatforms = ['paper', 'bitget'].filter((key) => platforms?.[key]?.enabled !== false);
|
||||
const haltedCount = countHalted(halts);
|
||||
const disabledCount = Object.values(executionControls).filter((item) => item && item.enabled === false).length;
|
||||
const runtimeTone = toneClassForHealth(cryptoAgent.running ? monitor.last_cycle_status || monitor.last_analysis_status : 'stopped');
|
||||
const riskTone = haltedCount > 0 ? 'danger' : ((disabledCount > 0) || (data.management?.attention_items || []).some((item) => item.severity === 'danger' || item.severity === 'warning') ? 'warn' : 'good');
|
||||
const platformTone = haltedCount > 0 || disabledCount > 0 ? 'warn' : 'good';
|
||||
const platformHeadline = `${enabledPlatforms.length} 平台`;
|
||||
const platformDetail = `${sumPlatformPositions(platforms)} 持仓 / ${['paper', 'bitget'].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} 停机` : (disabledCount > 0 ? `${disabledCount} 关闭` : '无停机');
|
||||
const attentionItems = data.management?.attention_items || [];
|
||||
const riskDetail = haltedCount > 0
|
||||
? '已有平台触发停机或熔断,建议优先查看。'
|
||||
: disabledCount > 0
|
||||
? `有 ${disabledCount} 个执行目标被人工关闭自动交易。`
|
||||
: attentionItems.length > 0
|
||||
? `待处理 ${attentionItems.length} 项,建议检查风险与执行事件。`
|
||||
: '当前没有明显风险阻塞。';
|
||||
const executionLoop = data.monitoring?.execution_loop || {};
|
||||
const priceMonitor = data.monitoring?.price_monitor || {};
|
||||
const latestPriceCount = Object.keys(priceMonitor.latest_prices || {}).length;
|
||||
|
||||
container.innerHTML = `
|
||||
<button class="ops-summary-card ${platformTone}" data-ops-target="opsPlatform">
|
||||
<div class="ops-summary-kicker">平台概览</div>
|
||||
<div class="ops-summary-headline ${platformTone}">${platformHeadline}</div>
|
||||
<div class="ops-summary-detail">${platformDetail}</div>
|
||||
</button>
|
||||
<button class="ops-summary-card ${runtimeTone}" data-ops-target="opsRuntime">
|
||||
<div class="ops-summary-kicker">心跳日志</div>
|
||||
<div class="ops-summary-headline ${runtimeTone}">${runtimeHeadline}</div>
|
||||
<div class="ops-summary-detail">${runtimeDetail}</div>
|
||||
</button>
|
||||
<button class="ops-summary-card ${riskTone}" data-ops-target="opsRisk">
|
||||
<div class="ops-summary-kicker">停机熔断</div>
|
||||
<div class="ops-summary-headline ${riskTone}">${riskHeadline}</div>
|
||||
<div class="ops-summary-detail">${riskDetail}</div>
|
||||
</button>
|
||||
<div class="monitor-card">
|
||||
<div class="title">价格链路</div>
|
||||
<div class="main">${priceMonitor.running ? 'RUNNING' : 'STOPPED'} / ${(priceMonitor.mode || 'unknown').toUpperCase()}</div>
|
||||
<div class="meta">
|
||||
订阅标的 ${(priceMonitor.subscribed_symbols || []).length || 0} / 缓存价格 ${latestPriceCount}<br>
|
||||
最近刷新 ${priceMonitor.checked_at ? formatTime(priceMonitor.checked_at) : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="monitor-card">
|
||||
<div class="title">模拟盘执行链路</div>
|
||||
<div class="main">${executionLoop.running ? 'RUNNING' : 'STOPPED'}</div>
|
||||
<div class="meta">
|
||||
心跳 ${executionLoop.last_heartbeat_at ? `${relativeTime(executionLoop.last_heartbeat_at)} / ${formatTime(executionLoop.last_heartbeat_at)}` : '-'}<br>
|
||||
活跃订单 ${executionLoop.active_orders ?? 0} / 标的 ${Array.isArray(executionLoop.last_symbols) && executionLoop.last_symbols.length ? executionLoop.last_symbols.join(', ') : '-'}<br>
|
||||
错误 ${executionLoop.last_error || '无'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.querySelectorAll('[data-ops-target]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
setActiveTab('ops', button.getAttribute('data-ops-target'));
|
||||
});
|
||||
});
|
||||
|
||||
setActiveTab('ops', getActiveTabTarget('ops') || 'opsPlatform');
|
||||
}
|
||||
|
||||
function renderFocusSummary(data) {
|
||||
@ -3228,39 +3420,9 @@
|
||||
const executionEvents = data.execution_events || [];
|
||||
const positions = data.management?.positions || [];
|
||||
const orders = data.management?.orders || [];
|
||||
const platformCount = ['paper', 'bitget']
|
||||
.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 disabledCount = Object.values(data.crypto_agent?.target_execution_controls || {}).filter((item) => item && item.enabled === false).length;
|
||||
const runtimeCount = (data.crypto_agent?.recent_analysis_events || []).length + (data.crypto_agent?.analysis_monitor?.last_heartbeat_at ? 1 : 0);
|
||||
const blockedCount = (data.execution_events || []).filter((event) => event.event_type === 'execution_blocked_summary').length;
|
||||
|
||||
updateTabButton('command', 'commandOverview', '总览', coordinationCount + platformCount, coordinationCount + platformCount > 0);
|
||||
updateTabButton('command', 'commandRisk', '风险', haltedCount + disabledCount + blockedCount, haltedCount + disabledCount + blockedCount > 0);
|
||||
updateTabButton('command', 'commandRuntime', '运行', runtimeCount, runtimeCount > 0);
|
||||
updateTabButton('command', 'commandLogs', '日志', executionEvents.length + runtimeCount, executionEvents.length + runtimeCount > 0);
|
||||
updateTabButton('logs', 'logsExecution', '执行事件', executionEvents.length, executionEvents.length > 0);
|
||||
updateTabButton('logs', 'logsAnalysis', '分析日志', runtimeCount, runtimeCount > 0);
|
||||
updateTabButton('workspace', 'workspaceCoordination', '账户协同', coordinationCount, coordinationCount > 0);
|
||||
updateTabButton('workspace', 'workspaceSignals', '信号缓存', recentSignals.length, recentSignals.length > 0);
|
||||
updateTabButton('asset', 'assetPositions', '持仓', positions.length, positions.length > 0);
|
||||
updateTabButton('asset', 'assetOrders', '挂单', orders.length, orders.length > 0);
|
||||
|
||||
const commandCurrent = getActiveTabTarget('command');
|
||||
const commandChoices = [
|
||||
{ target: 'commandRisk', hasData: haltedCount + disabledCount + blockedCount > 0 },
|
||||
{ target: 'commandOverview', hasData: coordinationCount + platformCount > 0 },
|
||||
{ target: 'commandRuntime', hasData: runtimeCount > 0 },
|
||||
{ target: 'commandLogs', hasData: executionEvents.length + runtimeCount > 0 },
|
||||
];
|
||||
if (!commandChoices.find((item) => item.target === commandCurrent && item.hasData)) {
|
||||
setActiveTab('command', commandChoices.find((item) => item.hasData)?.target || 'commandOverview');
|
||||
}
|
||||
|
||||
const logsCurrent = getActiveTabTarget('logs');
|
||||
const logsChoices = [
|
||||
@ -3271,23 +3433,11 @@
|
||||
setActiveTab('logs', logsChoices.find((item) => item.hasData)?.target || 'logsExecution');
|
||||
}
|
||||
|
||||
const workspaceCurrent = getActiveTabTarget('workspace');
|
||||
const workspaceChoices = [
|
||||
{ target: 'workspaceCoordination', hasData: coordinationCount > 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');
|
||||
}
|
||||
updateSidebarBadge('sidebarBadgeOverview', String((data.management?.attention_items || []).length));
|
||||
updateSidebarBadge('sidebarBadgeExecution', `${positions.length}/${orders.length}`);
|
||||
updateSidebarBadge('sidebarBadgeSignals', String(recentSignals.length));
|
||||
updateSidebarBadge('sidebarBadgeRuntime', data.crypto_agent?.analysis_monitor?.last_heartbeat_at ? 'live' : 'idle');
|
||||
updateSidebarBadge('sidebarBadgeLogs', String(executionEvents.length));
|
||||
}
|
||||
|
||||
function toneClassForHealth(status) {
|
||||
@ -4340,9 +4490,9 @@
|
||||
renderHealthRibbon(data);
|
||||
renderFocusSummary(data);
|
||||
renderAlertStrip(data);
|
||||
renderOpsSummary(data);
|
||||
renderTopPriority(data);
|
||||
renderOpsKpis(data);
|
||||
renderExecutionMonitor(data);
|
||||
renderSignalLifecycle(data);
|
||||
renderPlatforms(data.platforms, data.crypto_agent?.platform_halts, data.crypto_agent?.target_execution_controls || {});
|
||||
renderSignalStream(data.signals?.latest || []);
|
||||
@ -4396,6 +4546,7 @@
|
||||
|
||||
loadSensitivePreference();
|
||||
initTabs();
|
||||
initHubNavigation();
|
||||
applyAutoRefreshState();
|
||||
loadConsole();
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user