From 87fa8371655115698d685493c276f71c44d04013 Mon Sep 17 00:00:00 2001 From: martbost Date: Tue, 15 Sep 2026 07:31:16 -0500 Subject: [PATCH] Campaigns table: click sources stacked so the Clicks column stays narrow, card layout up to 900px; audit alert-day persisted, rounding-scale diffs ignored Co-Authored-By: Claude Fable 5.1 --- audit.js | 9 ++++++--- public/assets/my.js | 2 +- public/assets/site.css | 6 ++++-- public/my.html | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/audit.js b/audit.js index 44c5908..597c6d8 100644 --- a/audit.js +++ b/audit.js @@ -29,7 +29,7 @@ async function run() { const rows = await db.q('SELECT campaign_id, SUM(n) n FROM camp_hours GROUP BY campaign_id'); const h = {}; for (const r of rows) h[r.campaign_id] = Number(r.n); const issues = []; // only campaigns that started after the hourly log did - for (const c of camps.filter(c => ['banner', 'text'].includes(c.type) && Number(c.created) >= logStartMs)) { const l = h[c.id] || 0; if (Math.abs(Number(c.imps) - l) > 2) issues.push('#' + c.id + ' ' + c.name.slice(0, 30) + ': ' + c.imps + ' views vs ' + l + ' in the hourly log'); } + for (const c of camps.filter(c => ['banner', 'text'].includes(c.type) && Number(c.created) >= logStartMs)) { const l = h[c.id] || 0; if (Math.abs(Number(c.imps) - l) > Math.max(5, Math.round(0.02 * Math.max(Number(c.imps), l)))) issues.push('#' + c.id + ' ' + c.name.slice(0, 30) + ': ' + c.imps + ' views vs ' + l + ' in the hourly log'); } add('banner/text views vs hourly log', issues, camps.filter(c => ['banner', 'text'].includes(c.type) && Number(c.created) >= logStartMs).length + ' campaigns compared (started since the hourly log began ' + (logStart || '') + ')'); } // 3. login: days charged vs days shown (a day may only be charged after the ad was shown) @@ -58,11 +58,14 @@ async function run() { } return out; } -let lastAlertDay = ''; +// the "once a day" memory lives on the volume: a redeploy restarts the process, and Marty got an alert after every deploy (2026-09-15) +const STATE = () => path.join(R.dataDir, 'audit-state.json'); +function lastAlertDay() { try { return JSON.parse(fs.readFileSync(STATE(), 'utf8')).lastAlertDay || ''; } catch (e) { return ''; } } +function setAlertDay(d) { try { fs.writeFileSync(STATE(), JSON.stringify({ lastAlertDay: d })); } catch (e) {} } async function dailyTick() { try { const r = await run(); const bad = r.checks.filter(c => !c.ok); const day = new Date().toISOString().slice(0, 10); - if (bad.length && lastAlertDay !== day && R && R.notify) { lastAlertDay = day; R.notify('⚠️ InstantAdPay counter audit found ' + bad.length + ' issue' + (bad.length === 1 ? '' : 's') + ': ' + bad.map(c => c.name + ' (' + c.issues.length + ')').join('; ') + '. Admin > Reports > Counters audit.'); } + if (bad.length && lastAlertDay() !== day && R && R.notify) { setAlertDay(day); R.notify('⚠️ InstantAdPay counter audit found ' + bad.length + ' issue' + (bad.length === 1 ? '' : 's') + ': ' + bad.map(c => c.name + ' (' + c.issues.length + ')').join('; ') + '. Admin > Reports > Counters audit.'); } return r; } catch (e) { console.error('audit', e.message); return null; } } diff --git a/public/assets/my.js b/public/assets/my.js index 6442fae..48d7c39 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -809,7 +809,7 @@ + '' + c.type + (c.type === 'banner' && c.width ? ' ' + c.width + '×' + c.height + '' : '') + (c.dailyCap ? ' cap ' + c.dailyCap + '/day' : '') + (c.geo ? ' tier ' + esc(c.geo.replace(/,/g, '+')) + '' : '') + schedChips(c) + '' + '' + c.imps.toLocaleString() + '' + '' + (['banner', 'text'].includes(c.type) ? (c.impsNas || 0).toLocaleString() : 'n/a') + '' - + '' + c.clicks + (r.clickSources && r.clickSources[c.id] ? '
' + Object.entries(r.clickSources[c.id]).sort((a, b) => b[1] - a[1]).map(([k, v]) => esc(k) + ' ' + v).join(' · ') + (c.impsNas ? ' · network: see Network views' : '') + '
' : '') + '' + + '' + c.clicks + (r.clickSources && r.clickSources[c.id] ? '
' + Object.entries(r.clickSources[c.id]).sort((a, b) => b[1] - a[1]).map(([k, v]) => '' + esc(k) + ' ' + v + '').join('') + (c.impsNas ? ' · network: see Network views' : '') + '
' : '') + '' + '' + c.spent + '' + c.budget + '' + '' + (c.status === 'out' ? 'budget spent' : c.status === 'done' ? 'ended' : c.scheduled ? 'scheduled' : c.status) + '' + '' + (c.status === 'active' ? '' diff --git a/public/assets/site.css b/public/assets/site.css index 62a8d2a..a8b8d40 100644 --- a/public/assets/site.css +++ b/public/assets/site.css @@ -713,8 +713,10 @@ img{max-width:100%} .tk-table{width:100%;border-collapse:collapse;font-size:13px} .tk-table th,.tk-table td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)} .tk-table th{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)} .tk-table td.n{font-variant-numeric:tabular-nums;text-align:right} .tk-table th.n{text-align:right} .tk-table tr.best td{color:var(--mint)} .tk-team{border:1px solid var(--line);border-radius:10px;padding:8px 12px;margin:6px 0;font-size:13px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between} .tk-team .who b{font-weight:600} .tk-team .who span{color:var(--muted);font-size:12px} .tk-team.stalled{border-color:rgba(255,209,92,.45)} .tk-nudge{width:100%;margin:8px 0 0;padding:8px 12px;border:1px dashed var(--line);border-radius:10px;font-size:13px} -/* campaigns list on phones: each row becomes a card, buttons at the bottom, no sideways scroll (Jim, 2026-09-15) */ -@media (max-width:720px){ +/* click sources stack one per line so the Clicks column stays narrow (Marty, 2026-09-15) */ +.clk-src span{display:block;white-space:nowrap} .camp-table td{white-space:normal} .camp-table td:first-child{max-width:260px} +/* campaigns list on phones and tablets: each row becomes a card, buttons at the bottom, no sideways scroll (Jim, 2026-09-15) */ +@media (max-width:900px){ .tablewrap:has(.camp-table){overflow:visible} .camp-table{display:block;width:100%;border-collapse:separate} .camp-table thead{display:none} diff --git a/public/my.html b/public/my.html index ad81352..e3d9767 100644 --- a/public/my.html +++ b/public/my.html @@ -5,7 +5,7 @@ Member area | InstantAdPay - + @@ -984,7 +984,7 @@ - +