Sunstead trust scoring project
1<!doctype html>
2<meta charset="utf-8">
3<title>Tangled backfill — live</title>
4<style>
5 :root { color-scheme: dark; }
6 body { margin: 0; background: #0d1117; color: #e6edf3;
7 font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
8 header { padding: 18px 24px; border-bottom: 1px solid #21262d; display: flex;
9 align-items: baseline; gap: 16px; flex-wrap: wrap; }
10 h1 { font-size: 16px; margin: 0; font-weight: 600; }
11 .dot { width: 9px; height: 9px; border-radius: 50%; background: #3fb950;
12 display: inline-block; box-shadow: 0 0 8px #3fb950; animation: pulse 1.5s infinite; }
13 @keyframes pulse { 50% { opacity: .35; } }
14 .total { font-size: 28px; font-weight: 700; color: #58a6ff; }
15 .rate { color: #8b949e; }
16 main { padding: 16px 24px; max-width: 920px; }
17 .row { display: grid; grid-template-columns: 230px 1fr 90px; align-items: center;
18 gap: 12px; margin: 6px 0; }
19 .name { color: #c9d1d9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
20 .name.active { color: #3fb950; }
21 .bar { background: #161b22; border-radius: 4px; height: 20px; overflow: hidden; }
22 .fill { height: 100%; background: linear-gradient(90deg, #1f6feb, #58a6ff);
23 width: 0; transition: width .4s ease; }
24 .fill.active { background: linear-gradient(90deg, #238636, #3fb950); }
25 .num { text-align: right; color: #e6edf3; }
26 .zero .name { color: #6e7681; } .zero .num { color: #6e7681; }
27 footer { padding: 12px 24px; color: #8b949e; border-top: 1px solid #21262d; }
28 .derived span { margin-right: 18px; } .derived b { color: #e6edf3; }
29 .err { color: #f85149; }
30</style>
31<header>
32 <a href="/" style="display:block;width:100%;margin-bottom:8px"><img src="/static/logo.svg" alt="CyberCred" height="24"></a>
33 <span class="dot" id="dot"></span>
34 <h1>Tangled backfill</h1>
35 <span class="total" id="total">—</span>
36 <span class="rate" id="rate"></span>
37 <span class="rate" id="stamp"></span>
38</header>
39<main id="rows"></main>
40<footer class="derived" id="derived"></footer>
41<script>
42let prevTotal = null, prevT = null, peak = 1;
43const fmt = n => n.toLocaleString();
44
45async function tick() {
46 let d;
47 try {
48 d = await (await fetch('/backfill/status', {cache: 'no-store'})).json();
49 } catch (e) {
50 document.getElementById('stamp').innerHTML = '<span class="err">offline — is the API up?</span>';
51 return;
52 }
53 const now = Date.now();
54 document.getElementById('total').textContent = fmt(d.total);
55 if (prevTotal !== null && now > prevT) {
56 const rps = (d.total - prevTotal) / ((now - prevT) / 1000);
57 document.getElementById('rate').textContent = rps >= 0.5 ? `+${rps.toFixed(0)}/s` : '';
58 document.getElementById('dot').style.opacity = rps >= 0.5 ? 1 : 0.3;
59 }
60 document.getElementById('stamp').textContent = new Date().toLocaleTimeString();
61
62 const entries = Object.entries(d.collections);
63 peak = Math.max(peak, ...entries.map(([, n]) => n), 1);
64 const rows = document.getElementById('rows');
65 rows.innerHTML = entries.map(([name, n]) => {
66 const active = window._prev && window._prev[name] !== undefined && n > window._prev[name];
67 const w = (n / peak * 100).toFixed(1);
68 return `<div class="row ${n === 0 ? 'zero' : ''}">
69 <div class="name ${active ? 'active' : ''}">${active ? '▸ ' : ''}${name}</div>
70 <div class="bar"><div class="fill ${active ? 'active' : ''}" style="width:${w}%"></div></div>
71 <div class="num">${fmt(n)}</div></div>`;
72 }).join('');
73 window._prev = d.collections;
74
75 const x = d.derived;
76 document.getElementById('derived').innerHTML =
77 `derived → <span>contributors <b>${fmt(x.contributors)}</b></span>` +
78 `<span>vouches <b>${fmt(x.vouches)}</b></span>` +
79 `<span>pull_requests <b>${fmt(x.pull_requests)}</b></span>`;
80
81 prevTotal = d.total; prevT = now;
82}
83tick();
84setInterval(tick, 1500);
85</script>