feat(api): surface per-tenant storage row-counts on the operator dashboard#7201
Conversation
…board The account-wide D1 storage cap already has alerting (src/selfhost/d1-size-probe.ts, 70%/90% Prometheus alerts shipped as a direct follow-up to the #3810 incident), but it's table-level only -- no way to attribute usage to a specific tenant. This issue was already re-scoped to that specific gap (see the issue's own narrowing comment). Adds listRowCountByTenantSince (src/db/repositories.ts), a per-installation row-count breakdown of ai_usage_events -- the one high-growth table with a clean installationId column today, same GROUP BY shape as the existing listAiCostByTenantSince. Wires it into the operator dashboard payload and a new "Storage by tenant" UI section, following the exact same pattern PR #7191 established for AI cost by tenant. Empty for self-host, as with its sibling. Closes #4890
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | c5cc0e5 | Commit Preview URL Branch Preview URL |
Jul 18 2026, 12:10 PM |
Bundle ReportChanges will increase total bundle size by 1.01kB (0.01%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: loopover-uiAssets Changed:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7201 +/- ##
=======================================
Coverage 93.77% 93.77%
=======================================
Files 695 695
Lines 69042 69044 +2
Branches 18811 18811
=======================================
+ Hits 64746 64748 +2
Misses 3302 3302
Partials 994 994
Flags with carried forward coverage won't be shown. Click here to find out more.
|
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-18 12:19:37 UTC
Review summary Nits — 6 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Visual previewClick any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|




Summary
src/selfhost/d1-size-probe.ts, 70%/90% Prometheus alerts shipped as a direct follow-up to the obs(selfhost): monitor Cloudflare D1 database size and add bounded dedup retention for signal_snapshots #3810 incident) — but it's table-level only, with no way to attribute usage to a specific tenant. This issue was already re-scoped to that specific gap by an earlier narrowing comment on the issue itself.listRowCountByTenantSince(src/db/repositories.ts) — a per-installation row-count breakdown ofai_usage_events, the one high-growth table with a cleaninstallationIdcolumn today. SameGROUP BYshape as the existinglistAiCostByTenantSince(added in feat(api): surface per-tenant AI cost on the operator dashboard #7191) — row count is a plain, honest proxy for storage footprint here, since D1 has no per-row-group byte-size query surface.Closes #4890
Test plan
npm run typecheck— cleannpm run test:ci(full local gate, unsharded) — greentest/unit/ai-usage-tenant.test.ts: groups/orders by tenant correctly, excludes self-host (null-tenant) rows and rows outside the time window, returns[](not an error) with no hosted rowsapps/loopover-ui/src/routes/app.operator.test.tsx: no section when the field is absent/empty, renders each tenant's formatted row count highest-firstsrc/db/repositories.ts(mirrors the already-coveredlistAiCostByTenantSinceshape 1:1)AI cost by tenant); no new visual pattern introduced