Files
usage-report/index.html
T

268 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. 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>AgentStage 使用情况</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='7' fill='%23C4506A'/%3E%3C/svg%3E">
<style>
:root{
--rose:#C4506A;
--rose-dark:#a63f57;
--rose-100:#f9e8ec;
--rose-50:#fdf3f5;
--ink:#2D1B20;
--ink-70:rgba(45,27,32,.7);
--ink-50:rgba(45,27,32,.5);
--bg:#faf6f4;
--white:#ffffff;
--line:#efe3e6;
--good:#2e7d4f;
--good-bg:#e8f4ec;
--warn:#b07a1e;
--warn-bg:#fbf1dc;
--muted-bg:#f1eceb;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",Segoe UI,Roboto,sans-serif;
color:var(--ink);
background:var(--bg);
line-height:1.6;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:980px;margin:0 auto;padding:40px 24px 72px}
header{padding:34px 32px 28px;background:linear-gradient(135deg,#2D1B20 0%,#4a2430 55%,#C4506A 130%);border-radius:18px;color:#fff;margin-bottom:22px}
header .eyebrow{font-size:12px;letter-spacing:2px;opacity:.75;text-transform:uppercase}
header h1{font-size:28px;font-weight:700;margin-top:8px;letter-spacing:.5px}
header p{margin-top:10px;font-size:14px;opacity:.85}
.meta{display:inline-block;margin-top:16px;font-size:12.5px;padding:5px 13px;border-radius:999px;background:rgba(255,255,255,.14);letter-spacing:.5px}
.kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:30px}
.kpi{background:var(--white);border:1px solid var(--line);border-radius:14px;padding:18px 16px;text-align:left}
.kpi .num{font-size:30px;font-weight:700;color:var(--rose);font-variant-numeric:tabular-nums;line-height:1.1}
.kpi .lbl{font-size:12.5px;color:var(--ink-50);margin-top:6px;letter-spacing:.3px}
.kpi.alt .num{color:var(--ink)}
section{margin-bottom:32px}
.sec-head{display:flex;align-items:baseline;gap:10px;margin-bottom:14px}
.sec-head h2{font-size:19px;font-weight:700}
.sec-head .count{font-size:13px;color:var(--ink-50)}
.card{background:var(--white);border:1px solid var(--line);border-radius:14px;overflow:hidden}
table{width:100%;border-collapse:collapse;font-size:13.5px}
thead th{font-size:11.5px;text-transform:uppercase;letter-spacing:.8px;color:var(--ink-50);font-weight:600;text-align:left;padding:12px 16px;background:#faf3f4;border-bottom:1px solid var(--line)}
tbody td{padding:11px 16px;border-bottom:1px solid var(--line);vertical-align:top}
tbody tr:last-child td{border-bottom:none}
tbody tr:hover td{background:#fdf8f9}
.acct{font-weight:600;color:var(--ink)}
.acct .sub{font-weight:400;color:var(--ink-50);font-size:12px}
.pill{display:inline-block;font-size:11.5px;padding:2px 9px;border-radius:999px;font-weight:600;white-space:nowrap}
.pill.ok{background:var(--good-bg);color:var(--good)}
.pill.warn{background:var(--warn-bg);color:var(--warn)}
.pill.mut{background:var(--muted-bg);color:var(--ink-50)}
.pill.rose{background:var(--rose-100);color:var(--rose-dark)}
.num-mono{font-variant-numeric:tabular-nums;color:var(--ink-70)}
.repo-list{color:var(--ink-70);font-size:12.5px}
.repo-list .r{display:inline-block;background:var(--rose-50);color:var(--rose-dark);border-radius:6px;padding:1px 7px;margin:2px 3px 2px 0;font-size:11.5px}
.group{margin-bottom:18px}
.group>.g-title{font-size:14px;font-weight:700;margin-bottom:8px;display:flex;align-items:center;gap:8px}
.g-title .dot{width:8px;height:8px;border-radius:50%;display:inline-block}
.chips{display:flex;flex-wrap:wrap;gap:8px}
.chip{background:var(--white);border:1px solid var(--line);border-radius:999px;padding:6px 14px;font-size:13px;color:var(--ink-70)}
.chip b{color:var(--ink)}
.chip .tag{font-size:10.5px;color:var(--rose-dark);background:var(--rose-100);border-radius:4px;padding:1px 5px;margin-left:5px}
.finding{display:flex;gap:14px;padding:14px 16px;border-bottom:1px solid var(--line)}
.finding:last-child{border-bottom:none}
.finding .idx{flex:none;width:26px;height:26px;border-radius:8px;background:var(--rose-100);color:var(--rose-dark);font-weight:700;font-size:13px;display:flex;align-items:center;justify-content:center}
.finding .f-body{font-size:13.5px;color:var(--ink-70)}
footer{margin-top:36px;text-align:center;font-size:12px;color:var(--ink-50)}
.table-scroll{overflow-x:auto}
.empty{text-align:center;padding:28px;color:var(--ink-50);font-size:13.5px}
@media (max-width:760px){
.wrap{padding:20px 14px 56px}
header{padding:26px 22px 22px;border-radius:14px}
header h1{font-size:23px}
.kpis{grid-template-columns:repeat(3,1fr);gap:10px}
.kpi .num{font-size:24px}
.kpi{padding:14px 12px}
}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="eyebrow">AgentStage · Ops Report</div>
<h1>使用情况统计</h1>
<p>账号开通、登录、报告上线一屏总览,方便跟进未激活的同学。</p>
<span class="meta" id="gen-meta">统计时间:加载中…</span>
</header>
<div class="kpis">
<div class="kpi"><div class="num" id="k-total">—</div><div class="lbl">账号总数</div></div>
<div class="kpi"><div class="num" id="k-in">—</div><div class="lbl">已登录</div></div>
<div class="kpi alt"><div class="num" id="k-never">—</div><div class="lbl">从未登录</div></div>
<div class="kpi"><div class="num" id="k-reports">—</div><div class="lbl">已上线报告</div></div>
<div class="kpi alt"><div class="num" id="k-owners">—</div><div class="lbl">建仓人数</div></div>
</div>
<section>
<div class="sec-head"><h2>人员使用情况</h2><span class="count" id="c-users"></span></div>
<div class="group">
<div class="g-title"><span class="dot" style="background:#2e7d4f"></span><span id="g-active">已跑通 · 已登录且有报告</span></div>
<div class="card table-scroll">
<table>
<thead><tr><th>账号</th><th>最后登录</th><th>报告</th><th>已上线</th></tr></thead>
<tbody id="tb-active"></tbody>
</table>
</div>
</div>
<div class="group">
<div class="g-title"><span class="dot" style="background:#b07a1e"></span><span id="g-partial">已登录 · 还没建报告</span></div>
<div class="card table-scroll">
<table>
<thead><tr><th>账号</th><th>最后登录</th><th>钥匙</th><th>备注</th></tr></thead>
<tbody id="tb-partial"></tbody>
</table>
</div>
</div>
<div class="group">
<div class="g-title"><span class="dot" style="background:#C4506A"></span><span id="g-staff">管理员 / pilot</span></div>
<div class="card table-scroll">
<table>
<thead><tr><th>账号</th><th>角色</th><th>报告</th></tr></thead>
<tbody id="tb-staff"></tbody>
</table>
</div>
</div>
<div class="group">
<div class="g-title"><span class="dot" style="background:#9a8f8f"></span><span id="g-inactive">从未登录</span></div>
<div class="card" style="padding:16px">
<div class="chips" id="chips-inactive"></div>
</div>
</div>
</section>
<section>
<div class="sec-head"><h2>报告清单</h2><span class="count" id="c-reports"></span></div>
<div class="card table-scroll">
<table>
<thead><tr><th>报告</th><th>归属</th><th>自动刷新</th><th>最近刷新</th></tr></thead>
<tbody id="tb-reports"></tbody>
</table>
</div>
</section>
<section>
<div class="sec-head"><h2>关键发现</h2></div>
<div class="card" id="findings"></div>
</section>
<footer id="foot">AgentStage · 使用情况统计</footer>
</div>
<script>
function esc(s){
return String(s==null?'':s).replace(/[&<>"']/g, function(c){
return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];
});
}
function repoChips(repos){
if(!repos || !repos.length) return '<span class="num-mono">—</span>';
return '<span class="repo-list">'+repos.map(function(r){return '<span class="r">'+esc(r)+'</span>';}).join('')+'</span>';
}
function statusPill(s){
if(s==='auto') return '<span class="pill ok">有</span>';
if(s==='stale') return '<span class="pill warn">已停更</span>';
return '<span class="pill mut">静态</span>';
}
fetch('data.json')
.then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); })
.then(function(d){
document.getElementById('gen-meta').textContent = '统计时间:' + d.generated_at;
document.getElementById('k-total').textContent = d.summary.total;
document.getElementById('k-in').textContent = d.summary.logged_in;
document.getElementById('k-never').textContent = d.summary.never;
document.getElementById('k-reports').textContent = d.summary.reports;
document.getElementById('k-owners').textContent = d.summary.owners;
document.getElementById('c-users').textContent = '共 ' + d.summary.total + ' 人';
document.getElementById('c-reports').textContent = '共 ' + d.summary.reports + ' 份';
var el;
// active
document.getElementById('g-active').textContent =
'已跑通 · 已登录且有报告(' + d.active.length + ' 人)';
el = document.getElementById('tb-active');
el.innerHTML = d.active.length ? d.active.map(function(u){
return '<tr><td class="acct">'+esc(u.login)+'</td>'+
'<td class="num-mono">'+esc(u.last_login||'—')+'</td>'+
'<td class="num-mono">'+u.count+'</td>'+
'<td>'+repoChips(u.repos)+'</td></tr>';
}).join('') : '<tr><td colspan="4" class="empty">暂无</td></tr>';
// partial
document.getElementById('g-partial').textContent =
'已登录 · 还没建报告(' + d.partial.length + ' 人)';
el = document.getElementById('tb-partial');
el.innerHTML = d.partial.length ? d.partial.map(function(u){
var k = u.tk > 0 ? '<span class="pill ok">已生成</span>' : '<span class="pill warn">未生成</span>';
var note = u.tk > 0 ? '已生成钥匙,待上线首个报告' : '还没生成钥匙(token),需到设置里生成';
return '<tr><td class="acct">'+esc(u.login)+'</td>'+
'<td class="num-mono">'+esc(u.last_login||'—')+'</td>'+
'<td>'+k+'</td><td class="repo-list">'+note+'</td></tr>';
}).join('') : '<tr><td colspan="4" class="empty">暂无</td></tr>';
// staff
document.getElementById('g-staff').textContent = '管理员 / pilot(' + d.staff.length + ' 人)';
el = document.getElementById('tb-staff');
el.innerHTML = d.staff.map(function(u){
return '<tr><td class="acct">'+esc(u.login)+'</td>'+
'<td><span class="pill rose">'+esc(u.role)+'</span></td>'+
'<td>'+repoChips(u.repos)+'</td></tr>';
}).join('');
// inactive
document.getElementById('g-inactive').textContent = '从未登录(' + d.inactive.length + ' 人)';
el = document.getElementById('chips-inactive');
el.innerHTML = d.inactive.length ? d.inactive.map(function(u){
var tag = u.is_new ? '<span class="tag">新</span>' : '';
return '<span class="chip"><b>'+esc(u.login)+'</b>'+tag+'</span>';
}).join('') : '<span class="empty">暂无</span>';
// reports
el = document.getElementById('tb-reports');
el.innerHTML = d.reports.length ? d.reports.map(function(r){
return '<tr><td class="acct">'+esc(r.name)+'</td>'+
'<td>'+esc(r.owner)+'</td>'+
'<td>'+statusPill(r.status)+'</td>'+
'<td class="num-mono">'+esc(r.last_refresh||'—')+'</td></tr>';
}).join('') : '<tr><td colspan="4" class="empty">暂无</td></tr>';
// findings
el = document.getElementById('findings');
el.innerHTML = d.findings.length ? d.findings.map(function(f,i){
return '<div class="finding"><div class="idx">'+(i+1)+'</div><div class="f-body">'+esc(f)+'</div></div>';
}).join('') : '<div class="empty">暂无</div>';
document.getElementById('foot').textContent = 'AgentStage · 使用情况统计 · 数据生成于 ' + d.generated_at;
})
.catch(function(e){
document.getElementById('gen-meta').textContent = '数据加载失败:' + e.message;
});
</script>
</body>
</html>