Admin CSV export (sources snapshot + audience time-series)

Per Codex v1 — boring-in-the-best-way: inspect/archive operational data outside
the app. Admin-gated, Python csv module, text/csv + attachment disposition.

* GET /api/admin/export/sources.csv — current-state snapshot per source: name,
  feed/homepage, status, visible, served/accepted/total, acceptance/duplicate/
  accepted-dup/image-coverage %, last success/error, retry-after, review.
* GET /api/admin/export/audience.csv?days= — summary block (visitors, returning,
  accounts, feedback, shares) + a blank line + the daily visits/opens series;
  range applies to audience, sources is a snapshot.
* source_health now also returns feed_url/homepage. Small download links on the
  Sources + Audience tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-09 13:05:09 -04:00
parent 26014297f4
commit 1cd7f1d89a
4 changed files with 95 additions and 2 deletions
+5 -1
View File
@@ -434,7 +434,7 @@
</section>
{/if}
<h2>Sources</h2>
<h2>Sources <a class="exportlink" href="/api/admin/export/sources.csv" download>export CSV ↓</a></h2>
<p class="sub2">{healthy} healthy · {resting} resting · {flagged} flagged · {paused} paused · {retired} retired · {sources.length} total</p>
<div class="filterchips">
{#each [['all', 'All'], ['healthy', 'Healthy'], ['resting', 'Resting'], ['flagged', 'Flagged'], ['paused', 'Paused'], ['retired', 'Retired']] as [key, label] (key)}
@@ -490,6 +490,7 @@
<p class="legend2">“served” = accepted, non-duplicate articles live · accept/dup % is of all ingested · pausing stops polling but keeps existing articles live · times in your local zone</p>
{:else if section === 'audience'}
<p class="exporthdr"><a class="exportlink" href={'/api/admin/export/audience.csv?days=' + range} download>export audience CSV ({range}d) ↓</a></p>
<section>
<h2>Visitors</h2>
<div class="cards">
@@ -740,6 +741,9 @@
.legend .sw.opens { background: var(--accent); }
.sub2 { color: var(--muted); font-size: 0.84rem; margin: 0 0 12px; }
.exporthdr { margin: 0 0 14px; }
.exportlink { font-size: 0.78rem; font-weight: 400; color: var(--accent-deep); margin-left: 10px; }
.exportlink:hover { text-decoration: underline; }
.legend2 { color: var(--muted); font-size: 0.76rem; margin: 10px 0 0; font-style: italic; }
/* Analytics window picker */