279 lines
13 KiB
HTML
279 lines
13 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>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}
|
||
a.rep{color:var(--rose-dark);text-decoration:none}
|
||
a.rep:hover{text-decoration:underline}
|
||
.summ{color:var(--ink-70);font-size:12.5px}
|
||
.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><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 {'&':'&','<':'<','>':'>','"':'"',"'":'''}[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>';
|
||
}
|
||
function authPill(a){
|
||
if(a==='login') return '<span class="pill rose">需登录</span>';
|
||
if(a==='locked') return '<span class="pill warn">页面口令</span>';
|
||
return '<span class="pill ok">公开</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"><a class="rep" href="'+esc(r.url)+'" target="_blank" rel="noopener">'+esc(r.name)+'</a></td>'+
|
||
'<td class="summ">'+esc(r.summary||'—')+'</td>'+
|
||
'<td>'+esc(r.owner)+'</td>'+
|
||
'<td>'+authPill(r.auth)+'</td>'+
|
||
'<td>'+statusPill(r.status)+'</td>'+
|
||
'<td class="num-mono">'+esc(r.last_refresh||'—')+'</td></tr>';
|
||
}).join('') : '<tr><td colspan="6" 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>
|