Rsync page: add live sync activity log card left of settings
This commit is contained in:
@@ -43,6 +43,16 @@
|
||||
.vv-ry-run-dur { font-size:10px;color:#555;text-align:right; }
|
||||
.vv-ry-run-st { text-align:right; }
|
||||
|
||||
/* Sync log card */
|
||||
.vv-ry-slog-row { display:grid;grid-template-columns:38px 1fr 70px 50px 46px;gap:6px;align-items:center;
|
||||
padding:3px 0;border-bottom:1px solid #111; }
|
||||
.vv-ry-slog-row:last-child { border-bottom:none; }
|
||||
.vv-ry-slog-name { font-size:11px;color:#888;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
||||
.vv-ry-slog-dt { font-size:10px;color:#3a3a3a; }
|
||||
.vv-ry-slog-dur { font-size:10px;color:#555;text-align:right; }
|
||||
.vv-ry-slog-byt { font-size:10px;color:#555;text-align:right; }
|
||||
.vv-ry-slog-scrl { overflow-y:auto;max-height:200px; }
|
||||
|
||||
/* Settings */
|
||||
.vv-ry-set-grid { display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-top:6px; }
|
||||
.vv-ry-toggle { display:inline-flex;align-items:center;gap:8px;cursor:pointer;user-select:none; }
|
||||
@@ -720,6 +730,48 @@ function _bwSection(data) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Sync log card ─────────────────────────────────────────────────────────────
|
||||
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('');
|
||||
|
||||
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>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Settings section ──────────────────────────────────────────────────────────
|
||||
function _toggle(name, currentVal, label) {
|
||||
const id = 'vv-ry-tog-' + name;
|
||||
@@ -800,9 +852,30 @@ function _render(data) {
|
||||
html += _lastSyncCard(data);
|
||||
html += _windowsRow(data);
|
||||
html += _bwSection(data);
|
||||
html += _syncLogCard(data);
|
||||
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;
|
||||
}
|
||||
|
||||
// Restore any window panels that were open before the re-render
|
||||
_vvRyWinOpen.forEach(key => {
|
||||
const card = document.querySelector(`.vv-ry-win[data-win-key="${key}"]`);
|
||||
|
||||
Reference in New Issue
Block a user