dashboard.html•12.8 kB
{% extends "layout.html" %}
{% block title %}状态监控 - MCP Tools{% endblock %}
{% block content %}
<section class="dashboard-page">
<header class="dashboard-hero">
<div>
<span class="hero-pill">运行概况</span>
<h2 class="hero-title">实时状态监控中心</h2>
<p class="hero-subtitle">
聚合后台服务、浏览器会话与数据落盘状况,30 秒自动刷新,提供一目了然的可观测性面板。
</p>
</div>
<div class="hero-actions">
<a href="/admin/login" class="btn btn-primary">前往登录中心</a>
<button class="btn btn-secondary" type="button" onclick="refreshAllData()">立即刷新</button>
</div>
</header>
<div id="status-display" class="dashboard-status-strip">
<div class="status-placeholder">正在获取服务状态...</div>
</div>
<section class="metric-grid">
<article class="metric-card">
<span class="metric-icon">💻</span>
<span class="metric-label">CPU 使用率</span>
<span class="metric-value" id="cpu-percent">--%</span>
</article>
<article class="metric-card">
<span class="metric-icon">🧠</span>
<span class="metric-label">内存使用率</span>
<span class="metric-value" id="memory-percent">--%</span>
</article>
<article class="metric-card">
<span class="metric-icon">💾</span>
<span class="metric-label">磁盘使用率</span>
<span class="metric-value" id="disk-percent">--%</span>
</article>
<article class="metric-card">
<span class="metric-icon">📂</span>
<span class="metric-label">累计数据文件</span>
<span class="metric-value" id="total-files">--</span>
</article>
</section>
<section class="dashboard-grid">
<article class="card dashboard-card">
<div class="card-header">
<div class="card-eyebrow">服务健康</div>
<h2>核心服务状态</h2>
</div>
<div class="card-body">
<div class="service-grid" id="services-status">
<div class="service-item">
<span class="service-title">MCP 服务</span>
<span class="status" id="mcp-service-status">检查中...</span>
<span class="service-meta">端口 9090</span>
</div>
<div class="service-item">
<span class="service-title">管理界面</span>
<span class="status status-success">运行中</span>
<span class="service-meta">路径 /admin</span>
</div>
<div class="service-item">
<span class="service-title">数据库</span>
<span class="status" id="database-status">检查中...</span>
<span class="service-meta">PostgreSQL</span>
</div>
<div class="service-item">
<span class="service-title">Redis 缓存</span>
<span class="status" id="redis-status">检查中...</span>
<span class="service-meta">Redis</span>
</div>
</div>
</div>
</article>
<article class="card dashboard-card">
<div class="card-header">
<div class="card-eyebrow">登录态总览</div>
<h2>平台会话面板</h2>
</div>
<div class="card-body">
<div class="platform-groups" id="platforms-status">
<div class="platform-empty">正在加载平台状态...</div>
</div>
</div>
</article>
<article class="card dashboard-card">
<div class="card-header">
<div class="card-eyebrow">落盘监控</div>
<h2>数据持久化概览</h2>
</div>
<div class="card-body">
<div class="data-overview">
<div class="data-overview__item">
<span class="data-overview__label">文件总数</span>
<span class="data-overview__value" id="data-total-files">--</span>
</div>
<div class="data-overview__item">
<span class="data-overview__label">数据容量</span>
<span class="data-overview__value" id="data-total-size">-- MB</span>
</div>
<div class="data-overview__item data-overview__item--full">
<span class="data-overview__label">数据目录</span>
<span class="data-overview__value" id="data-path">--</span>
</div>
</div>
<div class="platform-data-grid" id="platform-data-stats">
<div class="status-placeholder">暂无爬取数据</div>
</div>
</div>
</article>
<article class="card dashboard-card">
<div class="card-header">
<div class="card-eyebrow">快捷入口</div>
<h2>高频操作</h2>
</div>
<div class="card-body">
<div class="quick-action-grid">
<a href="/admin/login" class="quick-action-card">
<span class="quick-action-icon">🔐</span>
<span class="quick-action-title">登录管理</span>
<span class="quick-action-desc">查看扫码、Cookie、手机号登录态</span>
</a>
<a href="/admin/config" class="quick-action-card">
<span class="quick-action-icon">⚙️</span>
<span class="quick-action-title">配置管理</span>
<span class="quick-action-desc">维护平台开关与爬虫参数</span>
</a>
<button type="button" class="quick-action-card" onclick="refreshAllData()">
<span class="quick-action-icon">🔄</span>
<span class="quick-action-title">刷新面板</span>
<span class="quick-action-desc">同步最新的服务与数据状态</span>
</button>
<a href="http://localhost:9090/sse" target="_blank" rel="noopener" class="quick-action-card">
<span class="quick-action-icon">🔌</span>
<span class="quick-action-title">SSE 终端</span>
<span class="quick-action-desc">打开 MCP SSE 流式输出</span>
</a>
</div>
</div>
</article>
</section>
</section>
{% endblock %}
{% block scripts %}
<script>
// Dashboard specific functions
async function loadSystemStatus() {
try {
const response = await apiRequest('/status/system');
document.getElementById('cpu-percent').textContent = `${response.cpu_percent || 0}%`;
document.getElementById('memory-percent').textContent = `${response.memory_percent || 0}%`;
document.getElementById('disk-percent').textContent = `${response.disk_usage_percent || 0}%`;
} catch (error) {
console.error('加载系统状态失败:', error);
}
}
async function loadServicesStatus() {
try {
const response = await apiRequest('/status/services');
updateServiceStatus('mcp-service-status', response.mcp_service);
updateServiceStatus('database-status', response.database);
updateServiceStatus('redis-status', response.redis);
} catch (error) {
console.error('加载服务状态失败:', error);
}
}
function updateServiceStatus(elementId, serviceStatus) {
const element = document.getElementById(elementId);
if (element && serviceStatus) {
element.textContent = serviceStatus.status === 'running' ? '运行中' : '未知';
element.className = `status ${serviceStatus.status === 'running' ? 'status-success' : 'status-error'}`;
}
}
async function loadPlatformsStatus() {
try {
const platforms = await apiRequest('/status/platforms');
const container = document.getElementById('platforms-status');
if (!platforms.length) {
container.innerHTML = '<div class="platform-empty">暂无平台状态信息</div>';
return;
}
const groups = [
{ title: '已登录', filter: (p) => p.has_session, statusLabel: '在线' },
{ title: '未登录', filter: (p) => !p.has_session, statusLabel: '离线' }
];
container.innerHTML = groups.map((group) => {
const groupItems = platforms.filter(group.filter);
const groupContent = groupItems.length
? groupItems.map((platform) => {
const metaParts = [];
metaParts.push(platform.enabled ? '已启用' : '停用');
metaParts.push(platform.tools_available ? '工具可用' : '工具受限');
if (platform.last_activity) {
metaParts.push(platform.last_activity);
}
return `
<div class="platform-chip ${platform.has_session ? 'is-active' : ''}">
<div class="platform-chip__details">
<span class="platform-chip__name">${platform.name}</span>
<span class="platform-chip__meta">${metaParts.join(' · ')}</span>
</div>
<span class="platform-chip__status">${group.statusLabel}</span>
</div>
`;
}).join('')
: '<div class="platform-group__empty">暂无平台</div>';
return `
<section class="platform-group">
<header class="platform-group__header">
<span class="platform-group__title">${group.title}</span>
<span class="platform-group__count">${groupItems.length}</span>
</header>
<div class="platform-group__list">
${groupContent}
</div>
</section>
`;
}).join('');
} catch (error) {
console.error('加载平台状态失败:', error);
}
}
async function loadDataStatus() {
try {
const response = await apiRequest('/status/data');
document.getElementById('data-total-files').textContent = response.total_files || 0;
document.getElementById('data-total-size').textContent = `${response.total_size_mb || 0} MB`;
document.getElementById('data-path').textContent = response.data_path || '--';
document.getElementById('total-files').textContent = response.total_files || 0;
const platformDataContainer = document.getElementById('platform-data-stats');
if (response.platforms && Object.keys(response.platforms).length > 0) {
platformDataContainer.innerHTML = Object.entries(response.platforms).map(([platform, stats]) => `
<div class="data-card">
<div class="data-card__header">
<span class="data-card__title">${platform}</span>
<span class="data-card__files">${stats.files_count} 个文件</span>
</div>
<div class="data-card__meta">
<span>容量 ${stats.total_size_mb} MB</span>
<span>最新 ${stats.latest_file || '未知'}</span>
</div>
</div>
`).join('');
} else {
platformDataContainer.innerHTML = '<div class="status-placeholder">暂无爬取数据</div>';
}
} catch (error) {
console.error('加载数据状态失败:', error);
}
}
async function refreshAllData() {
showMessage('正在刷新数据...', 'info');
await Promise.all([
loadSystemStatus(),
loadServicesStatus(),
loadPlatformsStatus(),
loadDataStatus()
]);
showSuccess('数据已刷新');
}
document.addEventListener('DOMContentLoaded', () => {
refreshAllData();
setInterval(refreshAllData, 30000);
});
</script>
{% endblock %}