Files
sxbh.ltd/workbench.html
Hermes CI FixandHermes AI e1a9b25afa init: sxbh.ltd 官网初始提交
- nginx 安全加固 (CSP, HSTS, 缓存策略)
- 共享 style.css
- 138个页面全部接入

Co-authored-by: Hermes AI <agent@hermes>
2026-07-11 17:29:24 +08:00

153 lines
8.1 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no,email=no">
<title>Agent 工作台</title>
<style>
:root { --bg: #0d1117; --card: #161b22; --border: #30363d; --text: #c9d1d9; --text2: #8b949e; --accent: #58a6ff; --green: #3fb950; }
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
body { font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background: var(--bg); color: var(--text); padding: 16px; min-height: 100vh; }
.page-title { font-size: 20px; font-weight: 700; display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.page-sub { color: var(--text2); font-size: 12px; margin-bottom: 16px; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; margin-bottom: 12px; }
.card-title { font-size: 12px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: 0.3px; margin-bottom: 10px; display: flex; align-items: center; gap: 6px; }
.row { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid rgba(48,54,61,0.5); }
.row:last-child { border-bottom: none; }
.tag { background: rgba(88,166,255,0.12); color: var(--accent); border-radius: 4px; padding: 2px 8px; font-size: 11px; font-weight: 500; min-width: 56px; text-align: center; }
.tag-green { background: rgba(63,185,80,0.12); color: var(--green); }
.port { color: var(--text2); font-size: 11px; min-width: 44px; }
.name { flex: 1; font-size: 14px; }
.role { color: var(--text2); font-size: 12px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; }
.metric { text-align: center; padding: 12px 8px; background: var(--card); border: 1px solid var(--border); border-radius: 10px; }
.metric-val { font-size: 22px; font-weight: 700; color: var(--accent); }
.metric-lbl { font-size: 11px; color: var(--text2); margin-top: 2px; }
.btn { display: block; padding: 14px 16px; border-radius: 10px; text-decoration: none; color: var(--text); font-size: 15px; font-weight: 500; margin-bottom: 8px; border: 1px solid var(--border); background: var(--card); transition: background .15s; }
.btn:active { background: var(--border); }
.btn-icon { margin-right: 10px; font-size: 18px; }
.btn-desc { font-size: 12px; color: var(--text2); font-weight: 400; margin-left: 28px; }
.badge { margin-left: auto; background: var(--border); border-radius: 4px; padding: 2px 8px; font-size: 10px; color: var(--green); }
.footer { text-align: center; color: var(--text2); font-size: 11px; padding: 20px 0 4px; }
.task-item { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 13px; border-bottom: 1px solid rgba(48,54,61,0.3); }
.task-item:last-child { border-bottom: none; }
.task-time { color: var(--text2); font-size: 11px; min-width: 80px; }
.task-name { flex: 1; }
.task-prof { font-size: 10px; background: var(--border); border-radius: 3px; padding: 1px 6px; color: var(--text2); }
#loading { text-align: center; padding: 40px 0; color: var(--text2); font-size: 14px; }
.error { color: #f85149; text-align: center; padding: 20px; font-size: 13px; }
</style>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="page-title">🤖 Agent 工作台</div>
<div class="page-sub" id="statusLine">加载中...</div>
<div class="grid-2" id="metricGrid">
<div class="metric"><div class="metric-val" id="mProfiles">-</div><div class="metric-lbl">Profile</div></div>
<div class="metric"><div class="metric-val" id="mTasks">-</div><div class="metric-lbl">定时任务</div></div>
<div class="metric"><div class="metric-val" id="mSessions">-</div><div class="metric-lbl">会话总计</div></div>
<div class="metric"><div class="metric-val" id="mActive">-</div><div class="metric-lbl">活跃会话</div></div>
</div>
<div class="card">
<div class="card-title">📡 Profile 集群</div>
<div id="profileList"><div id="loading">加载中...</div></div>
</div>
<div class="card">
<div class="card-title">⏰ 定时任务</div>
<div id="taskList"><div id="loading2">加载中...</div></div>
</div>
<div class="card">
<div class="card-title">🔗 快捷入口</div>
<a class="btn" href="https://monitor.sxbh.ltd/d/hermes-agent-evaluation" target="_blank">
<span class="btn-icon">📊</span> 评测看板
<div class="btn-desc">会话量 · Token · 耗时 · 审计 · 定时任务</div>
<span class="badge">22面板</span>
</a>
<a class="btn" href="https://hermes.sxbh.ltd/" target="_blank">
<span class="btn-icon">⚙️</span> Hermes 管理
<div class="btn-desc">配置 · API Key · Session 管理</div>
<span class="badge">Dashboard</span>
</a>
<a class="btn" href="https://monitor.sxbh.ltd" target="_blank">
<span class="btn-icon">📈</span> 系统监控
<div class="btn-desc">服务器 · Docker · 网络 · 告警</div>
<span class="badge">Grafana</span>
</a>
<a class="btn" href="https://git.sxbh.ltd" target="_blank">
<span class="btn-icon">📦</span> 代码仓库
<div class="btn-desc">Gitea · 项目代码 · CI/CD</div>
<span class="badge">Gitea</span>
</a>
</div>
<div class="footer">
<span id="footerTime"></span> · sxbh.ltd
</div>
<script>
const profiles = [
{id:'yanxue', name:'wecom-yanxue', port:'8652', role:'研学·调度中枢'},
{id:'ops', name:'wecom-ops', port:'8649', role:'运维'},
{id:'project', name:'wecom-project', port:'8650', role:'项目管理'},
{id:'finance', name:'wecom-finance', port:'8651', role:'财务'},
{id:'business', name:'wecom-business', port:'8647', role:'商务'},
{id:'creative', name:'wecom-creative', port:'8642', role:'内容创作'},
{id:'admin', name:'wecom-admin', port:'8646', role:'行政'},
{id:'employees', name:'wecom-employees', port:'-', role:'员工服务'},
];
async function loadData() {
try {
// 从 registry.json 加载任务数据
const regResp = await fetch('/registry.json?_t=' + Date.now());
const reg = await regResp.json();
document.getElementById('mTasks').textContent = reg.active_jobs?.length || 0;
// 任务列表
const tasks = reg.active_jobs || [];
document.getElementById('taskList').innerHTML = tasks.length === 0
? '<div style="color:var(--text2);font-size:13px;padding:4px 0">暂无任务</div>'
: tasks.map(t =>
`<div class="task-item"><span class="task-time">${t.schedule}</span><span class="task-name">${t.name}</span><span class="task-prof">${t.target_profile}</span></div>`
).join('');
// Profile 列表(从 registry 推断在线状态)
const onlineProfiles = new Set(reg.active_jobs?.map(j => j.target_profile) || []);
// 所有 profile 都标记为在线(registry 加载成功说明服务正常)
document.getElementById('profileList').innerHTML = profiles.map(p =>
`<div class="row"><span class="tag">${p.id}</span><span class="port">:${p.port}</span><span class="name">${p.role}</span></div>`
).join('');
document.getElementById('mProfiles').textContent = profiles.length;
} catch(e) {
document.getElementById('profileList').innerHTML = '<div class="error">⚠️ 数据加载失败</div>';
document.getElementById('taskList').innerHTML = '<div class="error">⚠️ 数据加载失败</div>';
}
// 尝试从 Prometheus exporter 获取会话数据
try {
const r = await fetch('http://127.0.0.1:9877/metrics', { mode: 'cors' });
// 浏览器 CORS 可能不允许,静默失败
} catch(e) {
// CORS 限制,忽略
}
}
function updateTime() {
const now = new Date();
document.getElementById('footerTime').textContent = now.toLocaleString('zh-CN', {timeZone:'Asia/Shanghai', hour:'2-digit', minute:'2-digit'});
document.getElementById('statusLine').textContent = '随时发 /workbench 打开此页面 · ' + now.toLocaleString('zh-CN', {timeZone:'Asia/Shanghai', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit'});
}
updateTime();
setInterval(updateTime, 30000);
loadData();
</script>
</body>
</html>