Add a P2P panel to the admin page
This commit is contained in:
@@ -187,6 +187,20 @@
|
||||
<div class="scroll"><table class="tbl" id="upTable"></table></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="row" style="margin-bottom:10px">
|
||||
<h2 style="margin:0">Peer-to-peer</h2>
|
||||
<span class="spacer"></span>
|
||||
<button id="p2pRefreshBtn">↻ Refresh</button>
|
||||
</div>
|
||||
<p class="muted">Devices share verified copies with each other (docs/p2p-architecture.md). A file's hash is only
|
||||
added after the server itself hashed and validated it. The malware scan is off by default
|
||||
(<code>P2P_MALWARE_SCAN=1</code> to enable). Holders are never expired — ones not re-checked for the
|
||||
stale period are only marked stale.</p>
|
||||
<div id="p2pSummary" class="msg"></div>
|
||||
<div class="scroll"><table class="tbl" id="p2pTable"></table></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="row" style="margin-bottom:10px">
|
||||
<h2 style="margin:0">Recent edits</h2>
|
||||
@@ -238,6 +252,35 @@
|
||||
$('logoutBtn').classList.add('hidden');
|
||||
}
|
||||
|
||||
async function loadP2p() {
|
||||
const j = await api('/api/admin/p2p');
|
||||
if (!j.ok) { $('p2pSummary').textContent = j.error || 'P2P unavailable'; return; }
|
||||
const c = j.config, s = j.stats;
|
||||
$('p2pSummary').textContent =
|
||||
`P2P ${c.enabled ? 'ON' : 'OFF'} · malware scan ${c.malwareScan ? 'ON' : 'off'} · stale after ${c.staleDays} d · ` +
|
||||
`keep ≥${c.keepMinViews} views/${c.keepDays} d or played in ${c.keepRecentDays} d — ` +
|
||||
`${s.content} verified (${s.revoked} revoked) · ${s.devices} devices, ${s.online} online · ` +
|
||||
`${s.holders} holdings over ${s.heldCids} files · intake ${s.intakeActive} running / ${s.intakeOpen} open`;
|
||||
const mb = (b) => (Number(b) / 1048576).toFixed(1) + ' MB';
|
||||
$('p2pTable').innerHTML = '<tr><th>Video</th><th>cid</th><th>Origin</th><th>Scan</th><th>Size</th><th>Verified</th><th>Holders</th><th></th></tr>' +
|
||||
j.recent.map((r) => {
|
||||
let title = '';
|
||||
try { title = JSON.parse(r.meta || '{}').title || ''; } catch { /* no meta */ }
|
||||
return `<tr${r.status === 'revoked' ? ' class="muted"' : ''}><td>${esc(title || r.video_id)}<br><small>${esc(r.video_id)}</small></td>` +
|
||||
`<td><code>${esc(r.cid.slice(0, 12))}</code></td><td>${esc(r.origin)}</td><td>${esc(r.scan)}</td><td>${mb(r.size)}</td>` +
|
||||
`<td>${esc(new Date(Number(r.verified_at)).toLocaleString())}</td><td>${r.holders}</td>` +
|
||||
`<td>${r.status === 'verified' ? `<button data-revoke="${esc(r.cid)}">Revoke</button>` : 'revoked'}</td></tr>`;
|
||||
}).join('');
|
||||
}
|
||||
$('p2pRefreshBtn').addEventListener('click', loadP2p);
|
||||
$('p2pTable').addEventListener('click', async (e) => {
|
||||
const b = e.target.closest('[data-revoke]');
|
||||
if (!b || !confirm('Revoke this file? Devices will stop sharing it and the server will never accept it again.')) return;
|
||||
const j = await api('/api/admin/p2p/revoke', { method: 'POST', body: { cid: b.dataset.revoke } });
|
||||
if (!j.ok) alert(j.error || 'failed');
|
||||
loadP2p();
|
||||
});
|
||||
|
||||
async function boot() {
|
||||
const me = await api('/api/admin/me');
|
||||
if (!me.enabled) { $('disabledBox').classList.remove('hidden'); return; }
|
||||
@@ -249,6 +292,7 @@
|
||||
loadTokens();
|
||||
loadRevs();
|
||||
loadUploads();
|
||||
loadP2p();
|
||||
const want = videoIdFrom(new URLSearchParams(location.search).get('v') || '');
|
||||
if (want) leOpen(want);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user