Admin > Reports: counters audit (views vs delivery logs, login days charged vs shown, featured views, clicks vs views, spend vs budget), daily with Telegram alert
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+7
-1
@@ -270,6 +270,12 @@
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-reports" hidden>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Counters audit</h3><span class="sub" id="audSub">views vs delivery logs, charges vs shows</span></div>
|
||||
<p class="small muted" style="margin:0 0 8px">Every format's recorded views are reconciled against the log that proves delivery, login days charged against days shown, and featured bookings checked for views. Runs daily and alerts you on Telegram; run it any time here.</p>
|
||||
<div style="margin:0 0 8px"><button type="button" class="btn small sec" id="audRun">Run now</button></div>
|
||||
<div class="tablewrap"><table class="adm-table" id="audTable"></table></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Ad reports</h3><span class="sub">members flagging ads</span></div>
|
||||
<div class="tablewrap"><table class="adm-table" id="repTable"></table></div>
|
||||
@@ -481,6 +487,6 @@
|
||||
</div>
|
||||
|
||||
<script src="/assets/common.js?v=20260914a"></script>
|
||||
<script src="/assets/admin.js?v=20260914d"></script>
|
||||
<script src="/assets/admin.js?v=20260915a"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -635,7 +635,19 @@
|
||||
document.querySelectorAll('#pnlPeriods [data-days]').forEach(b => b.addEventListener('click', () => { pnlDays = Number(b.dataset.days); document.querySelectorAll('#pnlPeriods [data-days]').forEach(x => x.classList.toggle('on', x === b)); loadPnl().catch(e => IAP.status(e.message, 'bad')); }));
|
||||
if ($('pnlFixedSave')) $('pnlFixedSave').addEventListener('click', async () => { try { await api('/api/admin/site', { pnlFixedMonthlyUsd: Number($('pnlFixed').value) || 0 }, 'PATCH'); IAP.status('Saved.', 'ok'); loadPnl(); } catch (e) { IAP.status(e.message, 'bad'); } });
|
||||
if ($('burnerRun')) $('burnerRun').addEventListener('click', async () => { try { const r = await api('/api/admin/burner/run', {}); IAP.status('Burner ran: ' + (r.burned || 0) + ' burned.', 'ok'); loadPnl(); } catch (e) { IAP.status(e.message, 'bad'); } });
|
||||
async function loadAudit() {
|
||||
if (!$('audTable')) return;
|
||||
try {
|
||||
$('audSub').textContent = 'checking…';
|
||||
const a = await api('/api/admin/audit');
|
||||
const bad = a.checks.filter(c => !c.ok).length;
|
||||
$('audSub').textContent = (bad ? bad + ' issue' + (bad === 1 ? '' : 's') : 'all counters reconcile') + ' · checked ' + new Date(a.checkedAt).toLocaleTimeString();
|
||||
$('audTable').innerHTML = '<tr><th>Check</th><th>Status</th><th>Detail</th></tr>' + a.checks.map(c => '<tr><td>' + esc(c.name) + '</td><td>' + (c.ok ? '<span class="badge">ok</span>' : '<span class="badge amber">' + c.issues.length + ' issue' + (c.issues.length === 1 ? '' : 's') + '</span>') + '</td><td class="small">' + esc(c.detail) + (c.issues.length ? '<br>' + c.issues.map(esc).join('<br>') : '') + '</td></tr>').join('');
|
||||
} catch (e) { $('audSub').textContent = e.message; }
|
||||
}
|
||||
if ($('audRun')) $('audRun').addEventListener('click', busy($('audRun'), loadAudit));
|
||||
async function loadReports() {
|
||||
loadAudit();
|
||||
const [r, b] = await Promise.all([api('/api/admin/reports'), api('/api/admin/burns')]);
|
||||
const reps = r.reports || [];
|
||||
$('repTable').innerHTML = reps.length ? '<tr><th>When</th><th>Campaign</th><th>Reason</th><th>Note</th><th>By</th><th></th></tr>'
|
||||
|
||||
Reference in New Issue
Block a user