stock-ai-agent/frontend/status.html
2026-02-22 11:56:51 +08:00

439 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统状态监控 - Stock Agent</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
border-radius: 16px;
padding: 30px;
margin-bottom: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 32px;
color: #333;
margin-bottom: 10px;
}
.header .subtitle {
color: #666;
font-size: 14px;
}
.system-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
background: white;
border-radius: 16px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
}
.stat-card .label {
color: #666;
font-size: 14px;
margin-bottom: 10px;
}
.stat-card .value {
font-size: 36px;
font-weight: bold;
color: #333;
}
.stat-card.running .value {
color: #10b981;
}
.stat-card.error .value {
color: #ef4444;
}
.agents-section {
background: white;
border-radius: 16px;
padding: 30px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.agents-section h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.agent-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
}
.agent-card {
border: 2px solid #e5e7eb;
border-radius: 12px;
padding: 20px;
transition: all 0.3s ease;
}
.agent-card:hover {
border-color: #667eea;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
}
.agent-card.status-running {
border-left: 4px solid #10b981;
}
.agent-card.status-error {
border-left: 4px solid #ef4444;
}
.agent-card.status-stopped {
border-left: 4px solid #f59e0b;
}
.agent-card.status-starting {
border-left: 4px solid #3b82f6;
}
.agent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.agent-name {
font-size: 18px;
font-weight: bold;
color: #333;
}
.agent-status {
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.agent-status.running {
background: #d1fae5;
color: #065f46;
}
.agent-status.error {
background: #fee2e2;
color: #991b1b;
}
.agent-status.stopped {
background: #fef3c7;
color: #92400e;
}
.agent-status.starting {
background: #dbeafe;
color: #1e40af;
}
.agent-info {
margin-top: 15px;
}
.agent-info-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #f3f4f6;
}
.agent-info-row:last-child {
border-bottom: none;
}
.agent-info-label {
color: #666;
font-size: 14px;
}
.agent-info-value {
color: #333;
font-size: 14px;
font-weight: 500;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.error-message {
background: #fee2e2;
color: #991b1b;
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
}
.refresh-button {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s ease;
}
.refresh-button:hover {
background: #5568d3;
}
.last-update {
text-align: right;
color: #666;
font-size: 12px;
margin-top: 10px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.pulse {
animation: pulse 2s infinite;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 系统状态监控</h1>
<p class="subtitle">实时监控所有 Agent 的运行状态</p>
<div style="margin-top: 20px;">
<button class="refresh-button" onclick="loadStatus()">🔄 刷新状态</button>
<span class="last-update" id="lastUpdate">加载中...</span>
</div>
</div>
<div id="errorContainer"></div>
<div class="system-overview">
<div class="stat-card">
<div class="label">运行时长</div>
<div class="value" id="uptime">-</div>
</div>
<div class="stat-card">
<div class="label">Agent 总数</div>
<div class="value" id="totalAgents">-</div>
</div>
<div class="stat-card running">
<div class="label">运行中</div>
<div class="value" id="runningAgents">-</div>
</div>
<div class="stat-card error">
<div class="label">错误</div>
<div class="value" id="errorAgents">-</div>
</div>
</div>
<div class="agents-section">
<h2>🤖 Agent 详情</h2>
<div id="agentsContainer">
<div class="loading">加载中...</div>
</div>
</div>
</div>
<script>
let refreshInterval;
function formatUptime(seconds) {
if (seconds < 60) {
return `${Math.floor(seconds)}`;
} else if (seconds < 3600) {
return `${Math.floor(seconds / 60)} 分钟`;
} else if (seconds < 86400) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours} 小时 ${minutes} 分钟`;
} else {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
return `${days}${hours} 小时`;
}
}
function getStatusIcon(status) {
const icons = {
'运行中': '✅',
'错误': '❌',
'已停止': '⏸️',
'启动中': '🔄',
'未初始化': '⚪'
};
return icons[status] || '❓';
}
function getStatusClass(status) {
const classes = {
'运行中': 'running',
'错误': 'error',
'已停止': 'stopped',
'启动中': 'starting',
'未初始化': 'stopped'
};
return classes[status] || '';
}
function formatSymbols(symbols) {
if (Array.isArray(symbols)) {
return symbols.join(', ');
}
return symbols || '-';
}
async function loadStatus() {
try {
const response = await fetch('/api/system/status');
const result = await response.json();
if (result.status === 'success') {
displayStatus(result.data);
} else {
showError('加载状态失败: ' + (result.message || '未知错误'));
}
// 更新最后刷新时间
const now = new Date();
document.getElementById('lastUpdate').textContent =
`最后更新: ${now.toLocaleTimeString('zh-CN')}`;
} catch (error) {
console.error('加载状态失败:', error);
showError('加载状态失败: ' + error.message);
}
}
function displayStatus(data) {
// 更新概览统计
document.getElementById('uptime').textContent = formatUptime(data.uptime_seconds);
document.getElementById('totalAgents').textContent = data.total_agents;
document.getElementById('runningAgents').textContent = data.running_agents;
document.getElementById('errorAgents').textContent = data.error_agents;
// 更新 Agent 列表
const container = document.getElementById('agentsContainer');
const agents = Object.entries(data.agents);
if (agents.length === 0) {
container.innerHTML = '<div class="loading">暂无 Agent 运行</div>';
return;
}
container.innerHTML = '<div class="agent-grid">' + agents.map(([id, agent]) => {
const statusClass = getStatusClass(agent.status);
const symbols = agent.config?.symbols || [];
return `
<div class="agent-card status-${statusClass}">
<div class="agent-header">
<div class="agent-name">${getStatusIcon(agent.status)} ${agent.name}</div>
<div class="agent-status ${statusClass}">${agent.status}</div>
</div>
<div class="agent-info">
<div class="agent-info-row">
<span class="agent-info-label">类型</span>
<span class="agent-info-value">${agent.type || '-'}</span>
</div>
<div class="agent-info-row">
<span class="agent-info-label">启动时间</span>
<span class="agent-info-value">${agent.start_time || '-'}</span>
</div>
<div class="agent-info-row">
<span class="agent-info-label">最后活动</span>
<span class="agent-info-value">${agent.last_activity || '-'}</span>
</div>
${symbols.length > 0 ? `
<div class="agent-info-row">
<span class="agent-info-label">监控标的</span>
<span class="agent-info-value">${formatSymbols(symbols)}</span>
</div>
` : ''}
${agent.config?.paper_trading_enabled !== undefined ? `
<div class="agent-info-row">
<span class="agent-info-label">模拟交易</span>
<span class="agent-info-value">${agent.config.paper_trading_enabled ? '✅ 已启用' : '❌ 未启用'}</span>
</div>
` : ''}
${agent.error_message ? `
<div class="agent-info-row">
<span class="agent-info-label">错误信息</span>
<span class="agent-info-value" style="color: #ef4444;">${agent.error_message}</span>
</div>
` : ''}
</div>
</div>
`;
}).join('') + '</div>';
}
function showError(message) {
const container = document.getElementById('errorContainer');
container.innerHTML = `<div class="error-message">${message}</div>`;
setTimeout(() => {
container.innerHTML = '';
}, 5000);
}
// 页面加载时获取状态
loadStatus();
// 每10秒自动刷新
refreshInterval = setInterval(loadStatus, 10000);
// 页面卸载时清除定时器
window.addEventListener('beforeunload', () => {
if (refreshInterval) {
clearInterval(refreshInterval);
}
});
</script>
</body>
</html>