feat(ui): add resource and usage dashboard (/usage route)

adds a new /usage page that lets board operators see how much each ai
provider is consuming across any date window, with per-model breakdowns,
rolling 5h/24h/7d burn windows, weekly budget bars, and a deficit notch
when projected spend is on track to exceed the monthly budget.

- new GET /companies/:id/costs/by-provider endpoint aggregates cost events
  by provider + model with pro-rated billing type splits from heartbeat runs
- new GET /companies/:id/costs/window-spend endpoint returns rolling window
  spend (5h, 24h, 7d) per provider with no schema changes
- QuotaBar: reusable boxed-border progress bar with green/yellow/red
  threshold fill colors and optional deficit notch
- ProviderQuotaCard: per-provider card showing budget allocation bars,
  rolling windows, subscription usage, and model breakdown with token/cost
  share overlays
- Usage page: date preset toggles (mtd, 7d, 30d, ytd, all, custom),
  provider tabs, 30s polling plus ws invalidation on cost_event
- custom date range blocks queries until both dates are selected and
  treats boundaries as local-time (not utc midnight) so full days are
  included regardless of timezone
- query key to timestamp is floored to the nearest minute to prevent
  cache churn on every 30s refetch tick
This commit is contained in:
Sai Shankar
2026-03-08 03:18:37 +05:30
committed by Dotta
parent 3dc3347a58
commit 94018e0239
15 changed files with 803 additions and 4 deletions

View File

@@ -62,6 +62,21 @@ export function costRoutes(db: Db) {
res.json(rows);
});
router.get("/companies/:companyId/costs/by-provider", async (req, res) => {
const companyId = req.params.companyId as string;
assertCompanyAccess(req, companyId);
const range = parseDateRange(req.query);
const rows = await costs.byProvider(companyId, range);
res.json(rows);
});
router.get("/companies/:companyId/costs/window-spend", async (req, res) => {
const companyId = req.params.companyId as string;
assertCompanyAccess(req, companyId);
const rows = await costs.windowSpend(companyId);
res.json(rows);
});
router.get("/companies/:companyId/costs/by-project", async (req, res) => {
const companyId = req.params.companyId as string;
assertCompanyAccess(req, companyId);