Admin Traffic tab: referring domains, landing pages, angles, by day

traffic.js logs public page views by referring domain (30 s buffered; page_hits
table or traffic.json), coach exposes all join-page views, and
/api/admin/traffic merges page views, join-page views, signups, registrations
and $20+ buyers by first-touch source for 7/30/90/365-day ranges.
Also: line-tree chips no longer truncate own-position labels.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-12 07:51:01 -05:00
parent abc76c47c8
commit ef088601c3
7 changed files with 131 additions and 4 deletions
+8 -2
View File
@@ -104,7 +104,8 @@ const J = {
async addView(token, angle, ts, ref) { this.db.views.push({ token, angle, ts, ref: ref || '' }); if (this.db.views.length > 50000) this.db.views = this.db.views.slice(-40000); this.save(); },
async addClick(campaignId, src, ts) { this.db.clicks = this.db.clicks || []; this.db.clicks.push({ campaignId, src, ts }); if (this.db.clicks.length > 50000) this.db.clicks = this.db.clicks.slice(-40000); this.save(); },
async clicksFor(ids) { return (this.db.clicks || []).filter(c => ids.includes(c.campaignId)); },
async views(tokens, since) { return this.db.views.filter(v => tokens.includes(v.token) && v.ts >= since); }
async views(tokens, since) { return this.db.views.filter(v => tokens.includes(v.token) && v.ts >= since); },
async viewsSince(since) { return this.db.views.filter(v => v.ts >= since); }
};
const D = {
async lastNudge(email) { const r = await db.q('SELECT rung, ts FROM nudges WHERE email=?', [email]); return r[0] ? { rung: r[0].rung, ts: Number(r[0].ts) } : null; },
@@ -131,6 +132,10 @@ const D = {
if (!tokens.length) return [];
const rows = await db.q('SELECT token, angle, ts, ref FROM join_views WHERE ts>=? AND token IN (' + tokens.map(() => '?').join(',') + ')', [since, ...tokens]);
return rows.map(r => ({ token: r.token, angle: r.angle, ts: Number(r.ts), ref: r.ref || '' }));
},
async viewsSince(since) {
const rows = await db.q('SELECT token, angle, ts, ref FROM join_views WHERE ts>=?', [since]);
return rows.map(r => ({ token: r.token, angle: r.angle, ts: Number(r.ts), ref: r.ref || '' }));
}
};
const impl = () => db.enabled() ? D : J;
@@ -249,4 +254,5 @@ function init(opts) {
DATA_DIR = opts.dataDir; chain = opts.chain; accounts = opts.accounts; mailer = opts.mailer;
J.load();
}
module.exports = { init, RUNGS, rungFor, describe, coachView, prospects, saveProspect, removeProspect, STATUSES, recordView, linkStats, nudgeTick, recordClick, clickSources, refHost };
async function viewsSince(since) { return impl().viewsSince(since); }
module.exports = { init, RUNGS, rungFor, describe, coachView, prospects, saveProspect, removeProspect, STATUSES, recordView, linkStats, nudgeTick, recordClick, clickSources, refHost, viewsSince };