2026-05-10 13:12:44 +08:00

424 lines
14 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🌊 光湖纪元 · 实时系统仪表盘</title>
<style>
:root {
--bg: #0d1117;
--card: #161b22;
--border: #30363d;
--text: #e6edf3;
--text2: #8b949e;
--accent: #58a6ff;
--green: #3fb950;
--yellow: #d29922;
--red: #f85149;
--purple: #bc8cff;
--pink: #f778ba;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
min-height: 100vh;
}
.header {
text-align: center;
padding: 2rem 1rem 1rem;
border-bottom: 1px solid var(--border);
}
.header h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
.header h1 span { color: var(--accent); }
.header .subtitle { color: var(--text2); font-size: 0.95rem; }
.status-bar {
display: flex;
justify-content: center;
gap: 1.5rem;
padding: 1rem;
flex-wrap: wrap;
}
.status-pill {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.4rem 1rem;
border-radius: 2rem;
background: var(--card);
border: 1px solid var(--border);
font-size: 0.85rem;
}
.status-pill .dot {
width: 8px; height: 8px; border-radius: 50%;
animation: pulse 2s infinite;
}
.dot.green { background: var(--green); }
.dot.yellow { background: var(--yellow); }
.dot.red { background: var(--red); }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
padding: 1.5rem;
max-width: 1200px;
margin: 0 auto;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.2rem;
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
}
.card.blue::before { background: var(--accent); }
.card.green::before { background: var(--green); }
.card.purple::before { background: var(--purple); }
.card.pink::before { background: var(--pink); }
.card.yellow::before { background: var(--yellow); }
.card .title {
font-size: 0.8rem;
color: var(--text2);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.8rem;
}
.card .value {
font-size: 2.2rem;
font-weight: 700;
line-height: 1;
}
.card .detail {
font-size: 0.8rem;
color: var(--text2);
margin-top: 0.5rem;
}
.wide-card {
grid-column: 1 / -1;
}
.section-title {
padding: 1.5rem 1.5rem 0.5rem;
max-width: 1200px;
margin: 0 auto;
font-size: 1.1rem;
color: var(--text2);
border-top: 1px solid var(--border);
}
/* Activity Timeline */
.timeline { list-style: none; }
.timeline li {
display: flex;
gap: 0.8rem;
padding: 0.6rem 0;
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
}
.timeline li:last-child { border-bottom: none; }
.timeline .time { color: var(--text2); min-width: 5rem; }
.timeline .event { flex: 1; }
/* Gauge */
.gauge-row {
display: flex;
align-items: center;
gap: 0.8rem;
margin-bottom: 0.5rem;
}
.gauge-row .label { font-size: 0.8rem; min-width: 5rem; color: var(--text2); }
.gauge-bar {
flex: 1;
height: 6px;
background: var(--border);
border-radius: 3px;
overflow: hidden;
}
.gauge-fill {
height: 100%;
border-radius: 3px;
transition: width 1s ease-out;
}
.gauge-fill.green { background: var(--green); }
.gauge-fill.blue { background: var(--accent); }
.gauge-fill.purple { background: var(--purple); }
.gauge-fill.yellow { background: var(--yellow); }
.gauge-row .pct { font-size: 0.8rem; min-width: 3rem; text-align: right; }
/* Footer */
.footer {
text-align: center;
padding: 2rem 1rem;
color: var(--text2);
font-size: 0.8rem;
border-top: 1px solid var(--border);
}
.footer a { color: var(--accent); text-decoration: none; }
/* Loading */
.loading {
text-align: center;
padding: 3rem;
color: var(--text2);
}
.spinner {
display: inline-block;
width: 2rem; height: 2rem;
border: 3px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Responsive */
@media (max-width: 600px) {
.grid { padding: 0.8rem; }
.card .value { font-size: 1.8rem; }
}
.blink { animation: blink-anim 1s steps(1) infinite; }
@keyframes blink-anim {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.refresh-btn {
background: var(--card);
border: 1px solid var(--border);
color: var(--accent);
padding: 0.4rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
margin-left: 0.5rem;
}
.refresh-btn:hover { background: var(--border); }
</style>
</head>
<body>
<div class="header">
<h1>🌊 <span>光湖纪元</span> · 实时系统仪表盘</h1>
<div class="subtitle">
AGE-5 · 数字地球操作系统 · 数据每次系统执行后自动更新
<button class="refresh-btn" onclick="loadData()">🔄 刷新</button>
</div>
</div>
<div id="status-bar" class="status-bar">
<div class="status-pill"><div class="dot green"></div> 加载中...</div>
</div>
<div id="content">
<div class="loading">
<div class="spinner"></div>
<div>正在从仓库加载实时数据...</div>
</div>
</div>
<div class="footer">
<p>
<a href="https://github.com/qinfendebingshuo/guanghulab">📦 返回仓库</a> ·
<a href="https://guanghulab.com">🌐 guanghulab.com</a> ·
数据源: <code>data/system-health.json</code>
</p>
<p style="margin-top:0.5rem">
光湖 HoloLake · 由冰朔创建 · 铸渊守护 · 🏛️ 国作登字-2026-A-00037559
</p>
<p id="last-update" style="margin-top:0.3rem"></p>
</div>
<script>
var REPO = 'qinfendebingshuo/guanghulab';
var BRANCH = 'main';
var HEALTH_PATH = 'data/system-health.json';
var BULLETIN_PATH = 'data/bulletin-board.json';
function rawUrl(p) {
return 'https://raw.githubusercontent.com/' + REPO + '/' + BRANCH + '/' + p + '?t=' + Date.now();
}
async function fetchJSON(url) {
var res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
}
function timeSince(dateStr) {
var d = new Date(dateStr);
var now = new Date();
var sec = Math.floor((now - d) / 1000);
if (sec < 60) return sec + '秒前';
var min = Math.floor(sec / 60);
if (min < 60) return min + '分钟前';
var hr = Math.floor(min / 60);
if (hr < 24) return hr + '小时前';
var day = Math.floor(hr / 24);
return day + '天前';
}
function statusColor(s) {
if (s === 'healthy') return 'green';
if (s === 'warning') return 'yellow';
return 'red';
}
function renderStatusBar(health) {
var bar = document.getElementById('status-bar');
var s = health.system_status || 'unknown';
var color = statusColor(s);
var statusText = s === 'healthy' ? ' 系统正常运转' : s === 'warning' ? ' 系统有告警' : ' 系统异常';
var updated = health.last_updated ? timeSince(health.last_updated) : '未知';
bar.innerHTML =
'<div class="status-pill"><div class="dot ' + color + '"></div> ' + statusText + '</div>' +
'<div class="status-pill">🕐 数据更新于 ' + updated + '</div>' +
'<div class="status-pill">🧬 神经系统 ' + (health.metrics.neural_system_version || 'v?') + '</div>' +
'<div class="status-pill">📜 本体论 ' + (health.metrics.ontology_version || 'v?') + '</div>';
}
function renderCards(health) {
var m = health.metrics;
var scan = health.last_skyeye_scan || {};
var html = '';
// Section: Core Metrics
html += '<div class="section-title">📊 核心指标 · Core Metrics</div>';
html += '<div class="grid">';
html += card('blue', '⚙️ 自动化流水线 (Workflows)', m.workflows.total,
'活跃 ' + m.workflows.active + ' · 失败 ' + m.workflows.failed + ' · 成功率 ' +
(m.workflows.total > 0 ? Math.round((m.workflows.active / m.workflows.total) * 100) : 0) + '%');
html += card('purple', '🤖 智能代理 (Agents)', m.agents.total,
'24 小时全天候自动运行');
html += card('pink', '🎭 AI 人格体 (Personas)', m.ai_personas.total,
'在线 ' + m.ai_personas.active + ' · 休眠 ' + m.ai_personas.hibernating);
html += card('green', '👥 人类开发者 (Developers)', m.human_developers.total,
'24h 活跃 ' + m.human_developers.active_24h + ' 人');
html += card('yellow', '🛡️ 系统守卫 (Guards)', m.guards.total,
'在线 ' + m.guards.active + ' · 告警 ' + m.guards.warnings);
html += card('blue', '📦 功能模块 (Modules)', m.modules.total,
'登录·人格·用户中心·工单·对话·云服务·看板·云盘·健康检查');
html += '</div>';
// Section: Performance
html += '<div class="section-title">⚡ 运行性能 · Performance</div>';
html += '<div class="grid">';
html += gaugeCard('系统健康', [
{ label: '流水线', pct: m.workflows.total > 0 ? Math.round((m.workflows.active / m.workflows.total) * 100) : 0, color: 'green' },
{ label: '守卫', pct: m.guards.total > 0 ? Math.round((m.guards.active / m.guards.total) * 100) : 0, color: 'blue' },
{ label: '人格体', pct: m.ai_personas.total > 0 ? Math.round((m.ai_personas.active / m.ai_personas.total) * 100) : 0, color: 'purple' },
{ label: '目录完整', pct: scan.directories ? parseFraction(scan.directories) : 100, color: 'green' }
]);
html += card('green', '⏱️ 部署耗时', '< 3 min', '从代码提交到生产环境上线');
html += card('blue', '🔁 每日自动化', '50+', '定时任务 + 事件触发 + 手动调度');
html += card('purple', '🔄 Notion 同步延迟', '< 60s', 'GitHub 代码变更 Notion 知识库');
html += '</div>';
// Section: SkyEye Scan
html += '<div class="section-title">🦅 天眼扫描 · SkyEye Scan</div>';
html += '<div class="grid">';
html += card('blue', '📋 最近扫描', scan.id || '—',
'流水线 ' + (scan.workflows || '?') + ' · 守卫 ' + (scan.guards || '?') + ' · 目录 ' + (scan.directories || '?'));
html += card(scan.issues > 0 ? 'yellow' : 'green', '🔍 扫描结果',
(scan.issues || 0) + ' 个问题',
'错误 ' + (scan.errors || 0) + ' · 告警 ' + (scan.warnings || 0));
html += card('green', '📜 本体论完整性 (D16)', scan.d16_ontology || '—', '6 条核心公理 · 3 层安全定义');
html += card('green', '🧬 神经映射完整性 (D20)', scan.d20_neural_map || '—', '11 核心流水线 · 5 Notion 知识库');
html += '</div>';
// Section: Hibernation
html += '<div class="section-title">🌙 休眠节律 · Hibernation</div>';
html += '<div class="grid">';
var hib = health.hibernation || {};
html += card('purple', '⭐ 周休眠', hib.next_weekly || '—', '全局快照 + 自愈扫描 + 系统优化');
html += card('blue', '🌙 日休眠', hib.next_daily || '—', '日检查点 + 健康探针');
html += card(health.alerts && health.alerts.length > 0 ? 'red' : 'green', '🚨 系统告警',
health.alerts && health.alerts.length > 0 ? health.alerts.length + ' 条告警' : '无告警',
health.alerts && health.alerts.length > 0 ? health.alerts.map(function(a) { return a; }).join(' · ') : '系统运行正常');
html += '</div>';
document.getElementById('content').innerHTML = html;
}
function card(color, title, value, detail) {
return '<div class="card ' + color + '">' +
'<div class="title">' + title + '</div>' +
'<div class="value">' + value + '</div>' +
'<div class="detail">' + detail + '</div>' +
'</div>';
}
function gaugeCard(title, gauges) {
var html = '<div class="card green"><div class="title">' + title + '</div>';
for (var i = 0; i < gauges.length; i++) {
var g = gauges[i];
html += '<div class="gauge-row">' +
'<div class="label">' + g.label + '</div>' +
'<div class="gauge-bar"><div class="gauge-fill ' + g.color + '" style="width:' + g.pct + '%"></div></div>' +
'<div class="pct">' + g.pct + '%</div>' +
'</div>';
}
html += '</div>';
return html;
}
function parseFraction(str) {
var parts = str.split('/');
if (parts.length === 2) {
var num = parseInt(parts[0]);
var den = parseInt(parts[1]);
return den > 0 ? Math.round((num / den) * 100) : 0;
}
return 100;
}
async function loadData() {
try {
var health;
try {
health = await fetchJSON(rawUrl(HEALTH_PATH));
} catch (e1) {
// Fallback: try loading from local path (for GitHub Pages deployment)
health = await fetchJSON('./system-health.json?t=' + Date.now());
}
renderStatusBar(health);
renderCards(health);
document.getElementById('last-update').textContent =
'🕐 数据加载于 ' + new Date().toLocaleString('zh-CN') +
' · 数据更新于 ' + (health.last_updated || '未知');
} catch (e) {
document.getElementById('content').innerHTML =
'<div class="loading" style="color:var(--red)">❌ 数据加载失败: ' + e.message +
'<br><br><button class="refresh-btn" onclick="loadData()">🔄 重试</button></div>';
}
}
// Auto-refresh every 5 minutes
loadData();
setInterval(loadData, 5 * 60 * 1000);
</script>
</body>
</html>