This commit is contained in:
aaron 2026-04-22 11:39:52 +08:00
parent 19780cf210
commit de5cd95742

View File

@ -131,7 +131,7 @@
.hero-subtitle {
margin: 14px 0 24px;
max-width: 780px;
max-width: 860px;
color: var(--muted);
font-size: 15px;
line-height: 1.7;
@ -148,6 +148,19 @@
border-radius: 18px;
background: var(--panel-soft);
border: 1px solid rgba(126, 200, 255, 0.08);
position: relative;
overflow: hidden;
}
.hero-metric::after {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(180deg, var(--cold), transparent);
opacity: 0.9;
}
.metric-label {
@ -172,6 +185,13 @@
color: var(--danger);
}
.metric-note {
margin-top: 6px;
color: var(--muted);
font-size: 11px;
font-family: "IBM Plex Mono", monospace;
}
.hero-side {
padding: 22px;
display: flex;
@ -247,7 +267,7 @@
.layout {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
grid-template-columns: minmax(0, 1.35fr) minmax(360px, 0.78fr);
gap: 18px;
}
@ -257,6 +277,30 @@
gap: 18px;
}
.section-label {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 999px;
background: rgba(126, 200, 255, 0.08);
border: 1px solid rgba(126, 200, 255, 0.16);
color: var(--cold);
font-size: 11px;
font-family: "IBM Plex Mono", monospace;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.section-label::before {
content: "";
width: 8px;
height: 8px;
border-radius: 999px;
background: currentColor;
box-shadow: 0 0 10px currentColor;
}
.panel {
padding: 22px;
}
@ -282,12 +326,107 @@
font-family: "IBM Plex Mono", monospace;
}
.priority-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(320px, 0.84fr);
gap: 18px;
margin-top: 18px;
}
.priority-main,
.priority-side {
display: grid;
gap: 18px;
}
.health-ribbon {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin-top: 12px;
}
.health-card {
padding: 14px 16px;
border-radius: 16px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.05);
}
.health-card .kicker {
color: var(--muted);
font-size: 11px;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.health-card .headline {
font-family: "IBM Plex Mono", monospace;
font-size: 14px;
color: var(--text);
}
.health-card .headline.good {
color: var(--good);
}
.health-card .headline.warn {
color: var(--warn);
}
.health-card .headline.danger {
color: var(--danger);
}
.health-card .detail {
margin-top: 6px;
color: var(--muted);
font-size: 11px;
line-height: 1.55;
font-family: "IBM Plex Mono", monospace;
}
.signal-grid,
.platform-grid {
display: grid;
gap: 14px;
}
.coord-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.coord-block {
padding: 14px;
border-radius: 18px;
background: rgba(255,255,255,0.025);
border: 1px solid rgba(255,255,255,0.06);
}
.coord-block .signal-grid {
grid-template-columns: 1fr;
}
.block-head {
margin-bottom: 12px;
}
.block-title {
margin: 10px 0 4px;
font-size: 16px;
font-weight: 600;
letter-spacing: -0.02em;
}
.block-sub {
color: var(--muted);
font-size: 12px;
font-family: "IBM Plex Mono", monospace;
}
.signal-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@ -401,6 +540,10 @@
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.platform-grid.compact {
grid-template-columns: 1fr;
}
.platform-card {
padding: 18px;
}
@ -467,6 +610,10 @@
padding: 18px;
}
.dense-panel .panel-header {
margin-bottom: 12px;
}
.stream-list {
display: grid;
gap: 10px;
@ -805,7 +952,7 @@
.ops-grid {
display: grid;
gap: 18px;
grid-template-columns: 0.9fr 1.1fr;
grid-template-columns: 1fr;
margin-top: 18px;
}
@ -967,13 +1114,15 @@
@media (max-width: 1240px) {
.hero,
.layout {
.layout,
.priority-layout {
grid-template-columns: 1fr;
}
.platform-grid,
.signal-grid,
.ops-grid {
.ops-grid,
.coord-grid {
grid-template-columns: 1fr;
}
@ -1000,7 +1149,8 @@
.hero-metrics,
.platform-stats,
.signal-stats,
.heartbeat-grid {
.heartbeat-grid,
.health-ribbon {
grid-template-columns: 1fr;
}
@ -1056,31 +1206,126 @@
<section class="panel">
<div class="panel-header">
<div>
<h2 class="panel-title">平台执行概览</h2>
<div class="panel-sub">资金、杠杆、持仓、挂单、回撤阈值</div>
<div class="section-label">Health Layer</div>
<h2 class="panel-title" style="margin-top: 12px;">系统健康总览</h2>
<div class="panel-sub">先判断系统活着没有,再判断风险和堵点</div>
</div>
</div>
<div class="platform-grid" id="platformGrid">
<div class="loading">正在加载平台状态...</div>
<div class="health-ribbon" id="healthRibbon">
<div class="health-card"><div class="kicker">分析状态</div><div class="headline">-</div></div>
<div class="health-card"><div class="kicker">最近轮次</div><div class="headline">-</div></div>
<div class="health-card"><div class="kicker">人工处理</div><div class="headline">-</div></div>
</div>
</section>
<section class="panel">
<section class="priority-layout">
<div class="priority-main">
<section class="panel">
<div class="panel-header">
<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="attention-list" id="attentionList">
<div class="loading">正在汇总待处理事项...</div>
</div>
</section>
<section class="panel dense-panel">
<div class="panel-header">
<div>
<div class="section-label">Platform Layer</div>
<h2 class="panel-title" style="margin-top: 12px;">平台执行概览</h2>
<div class="panel-sub">资金、杠杆、持仓、挂单、回撤阈值</div>
</div>
</div>
<div class="platform-grid" id="platformGrid">
<div class="loading">正在加载平台状态...</div>
</div>
</section>
</div>
<div class="priority-side">
<section class="panel dense-panel">
<div class="panel-header">
<div>
<div class="section-label">Runtime Layer</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>
<div class="heartbeat-card"><span class="label">最近轮次</span><span class="value">-</span></div>
<div class="heartbeat-card"><span class="label">当前进度</span><span class="value">-</span></div>
<div class="heartbeat-card"><span class="label">下一次运行</span><span class="value">-</span></div>
</div>
<div class="analysis-log-list" id="analysisLogList">
<div class="loading">正在读取分析日志...</div>
</div>
</section>
</div>
</section>
</div>
<div class="right-stack">
<section class="panel dense-panel">
<div class="panel-header">
<div>
<h2 class="panel-title">管理待处理事项</h2>
<div class="panel-sub">风险、停机、异常、待成交提醒</div>
<div class="section-label">Coordination Layer</div>
<h2 class="panel-title" style="margin-top: 12px;">信号与执行协同</h2>
<div class="panel-sub">把 Agent 状态和最近决策合并观察,减少来回切换</div>
</div>
</div>
<div class="attention-list" id="attentionList">
<div class="loading">正在汇总待处理事项...</div>
<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">最近决策预览</h3>
<div class="block-sub">最近一轮各平台准备执行的动作</div>
</div>
<div class="signal-grid" id="decisionPreview">
<div class="loading">正在读取决策预览...</div>
</div>
</div>
</div>
</section>
<section class="panel">
<section class="panel dense-panel">
<div class="panel-header">
<div>
<h2 class="panel-title">最近信号流</h2>
<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="halt-list" id="haltList">
<div class="loading">正在读取平台停机状态...</div>
</div>
<div class="footer-note">建议挂在大屏或副屏,默认每 15 秒刷新。</div>
</section>
</div>
</section>
<section class="layout" style="margin-top: 18px;">
<div class="left-stack">
<section class="panel dense-panel">
<div class="panel-header">
<div>
<div class="section-label">Market Layer</div>
<h2 class="panel-title" style="margin-top: 12px;">最近信号流</h2>
<div class="panel-sub">数据库最新信号与等级/置信度</div>
</div>
</div>
@ -1091,52 +1336,11 @@
</div>
<div class="right-stack">
<section class="panel">
<section class="panel dense-panel">
<div class="panel-header">
<div>
<h2 class="panel-title">Crypto Agent 状态</h2>
<div class="panel-sub">最近信号、平台停机、执行层状态</div>
</div>
</div>
<div class="signal-grid" id="agentSignals">
<div class="loading">正在读取 Agent 状态...</div>
</div>
</section>
<section class="panel">
<div class="panel-header">
<div>
<h2 class="panel-title">分析心跳</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>
<div class="heartbeat-card"><span class="label">最近轮次</span><span class="value">-</span></div>
<div class="heartbeat-card"><span class="label">当前进度</span><span class="value">-</span></div>
<div class="heartbeat-card"><span class="label">下一次运行</span><span class="value">-</span></div>
</div>
<div class="analysis-log-list" id="analysisLogList">
<div class="loading">正在读取分析日志...</div>
</div>
</section>
<section class="panel">
<div class="panel-header">
<div>
<h2 class="panel-title">最近决策预览</h2>
<div class="panel-sub">最近一轮各平台准备执行的动作</div>
</div>
</div>
<div class="signal-grid" id="decisionPreview">
<div class="loading">正在读取决策预览...</div>
</div>
</section>
<section class="panel">
<div class="panel-header">
<div>
<h2 class="panel-title">执行事件流</h2>
<div class="section-label">Execution Layer</div>
<h2 class="panel-title" style="margin-top: 12px;">执行事件流</h2>
<div class="panel-sub">最近执行结果、未执行原因、平台异常</div>
</div>
</div>
@ -1151,19 +1355,6 @@
<div class="loading">正在读取执行事件...</div>
</div>
</section>
<section class="stream-card">
<div class="panel-header">
<div>
<h2 class="panel-title">平台停机 / 熔断</h2>
<div class="panel-sub">出现风险时,这里是你第一眼要看的位置</div>
</div>
</div>
<div class="halt-list" id="haltList">
<div class="loading">正在读取平台停机状态...</div>
</div>
<div class="footer-note">建议挂在大屏或副屏,默认每 15 秒刷新。</div>
</section>
</div>
</section>
@ -1171,7 +1362,8 @@
<section class="panel unified-section">
<div class="panel-header">
<div>
<h2 class="panel-title">统一持仓视图</h2>
<div class="section-label">Position Layer</div>
<h2 class="panel-title" style="margin-top: 12px;">统一持仓视图</h2>
<div class="panel-sub">三端持仓合并,优先看风险与盈亏</div>
</div>
</div>
@ -1184,7 +1376,8 @@
<section class="panel unified-section">
<div class="panel-header">
<div>
<h2 class="panel-title">统一挂单视图</h2>
<div class="section-label">Order Layer</div>
<h2 class="panel-title" style="margin-top: 12px;">统一挂单视图</h2>
<div class="panel-sub">入场单、保护单、资金占用一屏观察</div>
</div>
</div>
@ -1200,6 +1393,7 @@
let autoRefresh = true;
let timer = null;
let currentEventFilter = 'all';
let cachedExecutionEvents = [];
function formatNumber(value, digits = 2) {
const num = Number(value || 0);
@ -1270,6 +1464,56 @@
el.innerHTML = `<div class="${isError ? 'error-box' : 'empty-box'}" style="margin-bottom: 18px;">${message}</div>`;
}
function toneClassForHealth(status) {
if (['error', 'failed', 'stopped'].includes(String(status || '').toLowerCase())) return 'danger';
if (['warning', 'halted', 'idle'].includes(String(status || '').toLowerCase())) return 'warn';
return 'good';
}
function renderHealthRibbon(data) {
const container = document.getElementById('healthRibbon');
const cryptoAgent = data.crypto_agent || {};
const monitor = cryptoAgent.analysis_monitor || {};
const attentionItems = data.management?.attention_items || [];
const dangerCount = attentionItems.filter((item) => item.severity === 'danger').length;
const warningCount = attentionItems.filter((item) => item.severity === 'warning').length;
const haltedCount = countHalted(cryptoAgent.platform_halts || {});
const cycleStatus = cryptoAgent.running
? String(monitor.last_cycle_status || 'waiting').toUpperCase()
: 'STOPPED';
const cycleTone = toneClassForHealth(cryptoAgent.running ? monitor.last_cycle_status : 'stopped');
const attentionTone = attentionItems.length === 0
? 'good'
: dangerCount > 0 || haltedCount > 0
? 'danger'
: 'warn';
const analysisTone = toneClassForHealth(monitor.last_analysis_status || 'idle');
const lastCycleText = monitor.last_cycle_completed_at
? `${relativeTime(monitor.last_cycle_completed_at)}`
: '尚无完成轮次';
const nextRunText = monitor.next_scheduled_run_at
? formatTime(monitor.next_scheduled_run_at)
: (monitor.current_cycle_total ? '轮次进行中' : '-');
container.innerHTML = `
<div class="health-card">
<div class="kicker">分析状态</div>
<div class="headline ${cycleTone}">${cycleStatus}</div>
<div class="detail">最近分析: ${(monitor.last_analysis_symbol || '-')} / ${(monitor.last_analysis_status || '-')}</div>
</div>
<div class="health-card">
<div class="kicker">最近轮次</div>
<div class="headline ${analysisTone}">${lastCycleText}</div>
<div class="detail">下一次运行: ${nextRunText}</div>
</div>
<div class="health-card">
<div class="kicker">人工处理</div>
<div class="headline ${attentionTone}">${attentionItems.length} 项</div>
<div class="detail">danger ${dangerCount} / warning ${warningCount} / halted ${haltedCount}</div>
</div>
`;
}
function renderHero(data) {
const heroMetrics = document.getElementById('heroMetrics');
const system = data.system || {};
@ -1282,18 +1526,22 @@
<div class="hero-metric">
<div class="metric-label">运行 Agent</div>
<div class="metric-value ${system.error_agents > 0 ? 'danger' : 'good'}">${system.running_agents || 0}/${system.total_agents || 0}</div>
<div class="metric-note">异常 ${system.error_agents || 0} 个</div>
</div>
<div class="hero-metric">
<div class="metric-label">30 分钟信号</div>
<div class="metric-value">${signals.recent_30m_count || 0}</div>
<div class="metric-note">最新分析窗口</div>
</div>
<div class="hero-metric">
<div class="metric-label">活跃持仓</div>
<div class="metric-value">${sumPlatformPositions(platforms)}</div>
<div class="metric-note">三端持仓合计</div>
</div>
<div class="hero-metric">
<div class="metric-label">停机平台</div>
<div class="metric-value ${haltedCount > 0 ? 'danger' : 'good'}">${haltedCount}</div>
<div class="metric-note">熔断或人工停机</div>
</div>
`;
@ -1599,9 +1847,10 @@
});
}
function renderExecutionEvents(events) {
function renderExecutionEvents(events = cachedExecutionEvents) {
const container = document.getElementById('eventStream');
const filtered = (events || []).filter((event) => currentEventFilter === 'all' || event.status === currentEventFilter);
cachedExecutionEvents = Array.isArray(events) ? events : [];
const filtered = cachedExecutionEvents.filter((event) => currentEventFilter === 'all' || event.status === currentEventFilter);
if (!filtered || filtered.length === 0) {
container.innerHTML = '<div class="empty-box">最近还没有执行事件</div>';
return;
@ -1790,6 +2039,7 @@
const data = result.data || {};
renderHero(data);
renderHealthRibbon(data);
renderPlatforms(data.platforms, data.crypto_agent?.platform_halts);
renderSignalStream(data.signals?.latest || []);
renderAgentSignals(data.crypto_agent, data.signals);
@ -1834,7 +2084,7 @@
document.getElementById('eventFilters').querySelectorAll('[data-filter]').forEach((item) => {
item.classList.toggle('active', item === button);
});
loadConsole();
renderExecutionEvents();
});
});