- nginx 安全加固 (CSP, HSTS, 缓存策略) - 共享 style.css - 138个页面全部接入 Co-authored-by: Hermes AI <agent@hermes>
153 lines
8.1 KiB
HTML
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>
|