424 lines
14 KiB
HTML
424 lines
14 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>🌊 光湖纪元 · 实时系统仪表盘</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>
|