Rsync sync log: stream live output from tee'd log file, preserve last run

This commit is contained in:
Gmer4Lfe
2026-06-06 16:06:42 -04:00
parent d05c5ac198
commit 57773bcaf5
3 changed files with 110 additions and 55 deletions
+57 -55
View File
@@ -735,47 +735,67 @@ function _esc(s) {
return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
let _liveTicker = null;
let _fastPollTimer = null;
function _syncLogCard(data) {
const active = data.active || [];
const history = data.bw_history || [];
const recent = [...history].reverse().slice(0, 14);
const now = Math.floor(Date.now() / 1000);
const activeRows = active.map(a => {
const startedTs = now - (a.elapsed || 0);
return `<div class="vv-ry-slog-row" style="background:#0a1a0a;border-radius:3px;margin-bottom:4px;padding:4px 6px;border:none;">
<span class="vv-ry-pill run">LIVE</span>
<span class="vv-ry-slog-name" style="color:#ccc;">${_esc(a.profile)}</span>
<span class="vv-ry-elapsed" data-started="${startedTs}"
style="font-size:11px;color:#4a9eff;font-family:monospace;grid-column:3/span 3;text-align:right;">${_dur(a.elapsed || 0)}</span>
</div>`;
}).join('');
const histRows = recent.map(r => {
const cls = r.status === 'success' ? 'on' : 'err';
const bytes = r.bytes > 0 ? _fmtBytes(r.bytes) : '';
return `<div class="vv-ry-slog-row">
<span class="vv-ry-pill ${cls}" style="font-size:9px;">${r.status === 'success' ? 'ok' : 'fail'}</span>
<span class="vv-ry-slog-name">${_esc(r.profile)}</span>
<span class="vv-ry-slog-dt">${r.date} ${r.time.slice(0,5)}</span>
<span class="vv-ry-slog-dur">${_dur(r.duration)}</span>
<span class="vv-ry-slog-byt">${bytes}</span>
</div>`;
}).join('');
let _logPollTimer = null;
let _logElapsed = 0;
let _logTicker = null;
function _syncLogCard() {
return `<div class="vv-ry-card" style="grid-column:span 5;display:flex;flex-direction:column;min-height:0;">
<div class="vv-ry-sec">Sync Activity</div>
${activeRows}
${activeRows && histRows ? '<hr class="vv-ry-sep">' : ''}
<div class="vv-ry-slog-scrl">
${histRows || '<div style="color:#333;font-size:11px;padding:6px 0;">No sync history</div>'}
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
<div class="vv-ry-sec" style="margin-bottom:0;">Sync Activity</div>
<span id="vv-ry-log-hdr" style="font-size:10px;color:#3a3a3a;"></span>
</div>
<div id="vv-ry-log-live"></div>
<div class="vv-ry-slog-scrl" id="vv-ry-log-lines"
style="font-family:monospace;font-size:10px;color:#888;white-space:pre-wrap;word-break:break-all;">
<span style="color:#333;">Loading…</span>
</div>
</div>`;
}
function _fetchLog() {
fetch('/plugins/varaverk/api/rsync.php?action=rsync_log')
.then(r => r.json())
.then(d => {
const linesEl = document.getElementById('vv-ry-log-lines');
const liveEl = document.getElementById('vv-ry-log-live');
const hdrEl = document.getElementById('vv-ry-log-hdr');
if (!linesEl) return;
if (d.live && d.profile) {
_logElapsed = d.elapsed || 0;
if (!_logTicker) {
_logTicker = setInterval(() => {
_logElapsed++;
const el = document.getElementById('vv-ry-log-elapsed');
if (el) el.textContent = _dur(_logElapsed);
}, 1000);
}
if (liveEl) liveEl.innerHTML =
`<div style="display:flex;align-items:center;gap:8px;background:#0a1a0a;border-radius:3px;padding:4px 8px;margin-bottom:4px;">
<span class="vv-ry-pill run">LIVE</span>
<span style="font-size:11px;color:#ccc;">${_esc(d.profile)}</span>
<span id="vv-ry-log-elapsed" style="margin-left:auto;font-size:11px;color:#4a9eff;font-family:monospace;">${_dur(d.elapsed || 0)}</span>
</div>`;
if (hdrEl) hdrEl.textContent = '';
} else {
if (_logTicker) { clearInterval(_logTicker); _logTicker = null; }
if (liveEl) liveEl.innerHTML = '';
if (hdrEl) hdrEl.textContent = d.profile ? 'last: ' + d.profile : '';
}
const atBottom = linesEl.scrollHeight - linesEl.scrollTop <= linesEl.clientHeight + 10;
linesEl.textContent = (d.lines || []).join('\n') || (d.profile ? '(no output captured)' : 'No sync history');
if (atBottom) linesEl.scrollTop = linesEl.scrollHeight;
})
.catch(() => {});
}
function _startLogPoll() {
_fetchLog();
if (!_logPollTimer) _logPollTimer = setInterval(_fetchLog, 2000);
}
// ── Settings section ──────────────────────────────────────────────────────────
function _toggle(name, currentVal, label) {
const id = 'vv-ry-tog-' + name;
@@ -856,29 +876,11 @@ function _render(data) {
html += _lastSyncCard(data);
html += _windowsRow(data);
html += _bwSection(data);
html += _syncLogCard(data);
html += _syncLogCard();
html += _settingsSection(data);
document.getElementById('vv-ry-grid').innerHTML = html;
// Live elapsed ticker — updates every second without re-fetching
if (_liveTicker) { clearInterval(_liveTicker); _liveTicker = null; }
if ((data.active || []).length) {
_liveTicker = setInterval(() => {
document.querySelectorAll('.vv-ry-elapsed').forEach(el => {
const s = parseInt(el.dataset.started, 10);
if (s) el.textContent = _dur(Math.floor(Date.now() / 1000) - s);
});
}, 1000);
}
// Fast-poll (5s) while syncs are active; return to 30s when idle
const hasActive = (data.active || []).length > 0;
if (hasActive && !_fastPollTimer) {
_fastPollTimer = setInterval(vvRyLoad, 5000);
} else if (!hasActive && _fastPollTimer) {
clearInterval(_fastPollTimer);
_fastPollTimer = null;
}
_startLogPoll();
// Restore any window panels that were open before the re-render
_vvRyWinOpen.forEach(key => {