Measure the link, not rsync: Data Transferred now reads Tailscale per-peer counters
The old card totalled rsync's own logs, so it reported 'no data moved' across a link that had carried hundreds of gigabytes over SSH, the arr APIs, conf pushes and the Unraid API.
This commit is contained in:
@@ -597,62 +597,84 @@ function _renderOfflineWarn(cfg) {
|
||||
|
||||
|
||||
// ── Data transferred ──────────────────────────────────────────────────────────
|
||||
//
|
||||
// Tailscale's per-peer counters, so this is every byte across the link — rsync, SSH, the arr
|
||||
// APIs, conf pushes, the Unraid API — not one tool's log of itself.
|
||||
//
|
||||
// A window says how far its samples actually reach. The sampler starts collecting the first
|
||||
// minute this ships, so "30 days" means thirty days of history only after thirty days of it;
|
||||
// until then the figure is real but covers less than the label, and saying which is the
|
||||
// difference between a number and a claim.
|
||||
function _vvBytes(b) {
|
||||
if (!b) return '0 B';
|
||||
const u = ['B','KB','MB','GB','TB'];
|
||||
let i = 0, v = b;
|
||||
while (v >= 1024 && i < u.length - 1) { v /= 1024; i++; }
|
||||
return (i >= 3 ? v.toFixed(2) : Math.round(v)) + ' ' + u[i];
|
||||
}
|
||||
|
||||
function _vvDur(s) {
|
||||
if (!s) return '';
|
||||
if (s < 3600) return Math.round(s / 60) + 'm';
|
||||
if (s < 86400) return (s / 3600).toFixed(1) + 'h';
|
||||
return (s / 86400).toFixed(1) + 'd';
|
||||
}
|
||||
|
||||
function _renderXfer(x) {
|
||||
const el = document.getElementById('vv-pt-xfer-body');
|
||||
if (!el) return;
|
||||
const peers = (x && x.peers) || {};
|
||||
const names = Object.keys(peers);
|
||||
if (!names.length) {
|
||||
el.innerHTML = `<div style="font-size:11px;color:#444;">No samples yet — the mesh sampler
|
||||
records once a minute. Figures appear within a minute or two.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
for (const p of names) {
|
||||
const d = peers[p];
|
||||
const live = d.live;
|
||||
html += `<div style="margin-bottom:6px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:baseline;gap:8px;
|
||||
padding-bottom:4px;border-bottom:1px solid #1b1b1b;">
|
||||
<span style="font-size:11px;color:#888;font-family:monospace;">${vvEscHtml(p)}</span>
|
||||
<span style="font-size:10px;color:${live && (live.tx_bps + live.rx_bps) > 1024 ? '#4caf50' : '#3a3a3a'};font-family:monospace;">
|
||||
${live ? '↑ ' + _vvBytes(live.tx_bps) + '/s ↓ ' + _vvBytes(live.rx_bps) + '/s' : 'idle'}
|
||||
</span>
|
||||
</div>`;
|
||||
|
||||
const rows = [['Last 24 hours','24h'], ['Last 7 days','7d'], ['Last 30 days','30d']];
|
||||
for (const [label, key] of rows) {
|
||||
const w = (d.windows || {})[key] || {};
|
||||
const total = (w.tx || 0) + (w.rx || 0);
|
||||
// A window whose samples do not span it yet is labelled with what it does cover, rather
|
||||
// than presenting a partial figure under a full-window heading.
|
||||
const partial = w.covers && w.covers < ({'24h':86400,'7d':604800,'30d':2592000}[key]) * 0.9;
|
||||
html += `<div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;
|
||||
padding:4px 0;border-bottom:1px solid #151515;">
|
||||
<span style="color:#666;font-size:11px;">${label}${partial
|
||||
? ` <span style="color:#3a3a3a;font-size:9px;">only ${_vvDur(w.covers)} recorded</span>` : ''}</span>
|
||||
<span style="font-family:monospace;font-size:11px;color:${total ? '#4caf50' : '#333'};">
|
||||
${total ? '↑ ' + _vvBytes(w.tx) + ' ↓ ' + _vvBytes(w.rx) : 'nothing'}
|
||||
</span></div>`;
|
||||
}
|
||||
|
||||
const s = d.session || {};
|
||||
html += `<div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:4px 0;">
|
||||
<span style="color:#666;font-size:11px;">Since tailscaled start</span>
|
||||
<span style="font-family:monospace;font-size:11px;color:#7a7a7a;">
|
||||
↑ ${_vvBytes(s.tx)} ↓ ${_vvBytes(s.rx)}</span>
|
||||
</div></div>`;
|
||||
}
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
//
|
||||
// A window with runs but no bytes says "no data moved", not "0.00 GB". They are different
|
||||
// claims: rsync completing with nothing to send is the normal state for a mirror that is
|
||||
// already in step, and printing a zero total invites the reading that the sync is broken.
|
||||
// A window with no runs at all says so separately again.
|
||||
function _renderXfer(x) {
|
||||
const el = document.getElementById('vv-pt-xfer-body');
|
||||
if (!el) return;
|
||||
x = x || {};
|
||||
const w = x.windows || {};
|
||||
|
||||
const fmt = (b) => {
|
||||
if (b === null || b === undefined) return null;
|
||||
const u = ['B','KB','MB','GB','TB'];
|
||||
let i = 0, v = b;
|
||||
while (v >= 1024 && i < u.length - 1) { v /= 1024; i++; }
|
||||
return (i >= 3 ? v.toFixed(2) : Math.round(v)) + ' ' + u[i];
|
||||
};
|
||||
|
||||
const row = (label, key) => {
|
||||
const d = w[key] || {};
|
||||
const bytes = fmt(d.bytes);
|
||||
let val, col;
|
||||
if (!d.runs) { val = 'no syncs ran'; col = '#333'; }
|
||||
else if (bytes) { val = bytes; col = '#4caf50'; }
|
||||
else { val = 'no data moved'; col = '#555'; }
|
||||
const extra = d.runs
|
||||
? `<span style="color:#333;font-size:9px;">${d.runs} run${d.runs===1?'':'s'}${d.failed?` · ${d.failed} failed`:''}</span>`
|
||||
: '';
|
||||
return `<div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;
|
||||
padding:5px 0;border-bottom:1px solid #171717;">
|
||||
<span style="color:#666;font-size:11px;">${label}</span>
|
||||
<span style="display:flex;align-items:baseline;gap:8px;">
|
||||
${extra}<span style="color:${col};font-size:12px;font-family:monospace;">${val}</span>
|
||||
</span></div>`;
|
||||
};
|
||||
|
||||
const live = x.live;
|
||||
const liveHtml = live
|
||||
? `<div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;
|
||||
padding:5px 0;border-bottom:1px solid #171717;">
|
||||
<span style="color:#666;font-size:11px;">Live</span>
|
||||
<span style="color:#ff9800;font-size:12px;font-family:monospace;">⟳ ${live.running} transfer${live.running===1?'':'s'}${live.share?` · ${vvEscHtml(live.share)}`:''}</span>
|
||||
</div>`
|
||||
: `<div style="display:flex;justify-content:space-between;align-items:baseline;gap:10px;
|
||||
padding:5px 0;border-bottom:1px solid #171717;">
|
||||
<span style="color:#666;font-size:11px;">Live</span>
|
||||
<span style="color:#333;font-size:12px;font-family:monospace;">idle</span>
|
||||
</div>`;
|
||||
|
||||
el.innerHTML = liveHtml + row('Last 24 hours','24h') + row('Last 7 days','7d') + row('Last 30 days','30d')
|
||||
+ `<div style="margin-top:7px;font-size:9px;color:#2e2e2e;line-height:1.5;">
|
||||
Counted from ${vvEscHtml(x.epoch || '')} — before that rsync logged every transfer as
|
||||
0 bytes, so those rows are excluded rather than summed into a false total.
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
// ── Config bar ────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user