SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
8.8 KB · 180 lines tsx
Raw Blame History
1import Link from "next/link";2import { Download } from "lucide-react";3import { requireAdmin } from "@/lib/session";4import { getEconomics, parseRange, rangeLabel } from "@/lib/queries/admin";5import { formatBytes, formatNumber, formatPercent, formatUsd } from "@/lib/format";6import { PageHeader } from "@/components/ui/page-header";7import { Button } from "@/components/ui/button";8import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";9import { MarginText, NetworkBadge, Panel, PlanBadge, ProviderBadge, RangeTabs, numCell } from "@/components/admin/primitives";10import { EconomicsChart } from "@/components/admin/charts";11import type { EconRow } from "@/lib/queries/admin";1213export const dynamic = "force-dynamic";1415function EconTable({ rows, labelHead, renderLabel }: { rows: EconRow[]; labelHead: string; renderLabel: (r: EconRow) => React.ReactNode }) {16  return (17    <Table>18      <TableHeader>19        <TableRow>20          <TableHead>{labelHead}</TableHead>21          <TableHead className="text-right">Requests</TableHead>22          <TableHead className="text-right">Success</TableHead>23          <TableHead className="text-right">Revenue</TableHead>24          <TableHead className="text-right">Upstream</TableHead>25          <TableHead className="text-right">Margin</TableHead>26          <TableHead className="text-right">GB</TableHead>27        </TableRow>28      </TableHeader>29      <TableBody>30        {rows.length === 0 ? (31          <TableEmpty colSpan={7}>No data in this range.</TableEmpty>32        ) : (33          rows.map((r) => (34            <TableRow key={r.key}>35              <TableCell>{renderLabel(r)}</TableCell>36              <TableCell className={numCell}>{formatNumber(r.requests)}</TableCell>37              <TableCell className={numCell}>{formatPercent(r.requests ? (r.successes / r.requests) * 100 : null)}</TableCell>38              <TableCell className={numCell}>{formatUsd(r.revenue, true)}</TableCell>39              <TableCell className={numCell}>{formatUsd(r.cost, true)}</TableCell>40              <TableCell className="text-right">41                <MarginText value={r.margin} pct={r.marginPct} />42              </TableCell>43              <TableCell className={numCell}>{formatBytes(r.bytes)}</TableCell>44            </TableRow>45          ))46        )}47      </TableBody>48    </Table>49  );50}5152export default async function AdminUsagePage({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {53  await requireAdmin();54  const sp = await searchParams;55  const range = parseRange(sp.range);56  const e = await getEconomics(range);57  const totals = e.byBucket.reduce((acc, r) => ({ revenue: acc.revenue + r.revenue, cost: acc.cost + r.cost, requests: acc.requests + r.requests }), { revenue: 0, cost: 0, requests: 0 });5859  return (60    <>61      <PageHeader62        eyebrow="Unit economics"63        title="Usage"64        description={`${rangeLabel(range)} · revenue is what customers are billed (price_usd); upstream cost is what providers charge us (cost_usd).`}65        actions={66          <>67            <RangeTabs current={range} basePath="/admin/usage" />68            <Button asChild variant="outline" size="sm">69              <Link href={`/admin/usage/export?range=${range}`} prefetch={false}>70                <Download className="size-3.5" /> Export CSV71              </Link>72            </Button>73          </>74        }75      />7677      <Panel title={`Revenue vs upstream cost by ${e.unit}`} description={`Total: ${formatUsd(totals.revenue)} revenue · ${formatUsd(totals.cost)} cost · ${formatNumber(totals.requests)} requests`} bodyClassName="p-2 pt-3">78        <EconomicsChart data={e.byBucket} unit={e.unit as "hour" | "day"} />79      </Panel>8081      <Panel title={`By ${e.unit}`} flush className="mt-6">82        <EconTable rows={e.byBucket} labelHead={e.unit === "hour" ? "Hour" : "Day"} renderLabel={(r) => <span className="font-mono text-[12.5px]">{e.unit === "hour" ? new Date(r.key).toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) : new Date(r.key).toLocaleDateString(undefined, { month: "short", day: "numeric" })}</span>} />83      </Panel>8485      <div className="mt-6 grid gap-6 xl:grid-cols-2">86        <Panel title="By plan" flush>87          <EconTable rows={e.byPlan} labelHead="Plan" renderLabel={(r) => <PlanBadge plan={r.key} />} />88        </Panel>89        <Panel title="By network" flush>90          <EconTable rows={e.byNetwork} labelHead="Network" renderLabel={(r) => <NetworkBadge network={r.key} />} />91        </Panel>92      </div>9394      <Panel title="By provider" description="Attempt-level. Attributed revenue = price of requests whose successful attempt ran on this provider." flush className="mt-6">95        <Table>96          <TableHeader>97            <TableRow>98              <TableHead>Provider</TableHead>99              <TableHead className="text-right">Attempts</TableHead>100              <TableHead className="text-right">Successes</TableHead>101              <TableHead className="text-right">Success</TableHead>102              <TableHead className="text-right">Upstream cost</TableHead>103              <TableHead className="text-right">Attributed revenue</TableHead>104              <TableHead className="text-right">Margin</TableHead>105              <TableHead className="text-right">Cost / success</TableHead>106              <TableHead className="text-right">GB</TableHead>107            </TableRow>108          </TableHeader>109          <TableBody>110            {e.byProvider.length === 0 ? (111              <TableEmpty colSpan={9}>No attempts in this range.</TableEmpty>112            ) : (113              e.byProvider.map((p) => (114                <TableRow key={p.provider}>115                  <TableCell>116                    <ProviderBadge id={p.provider} />117                  </TableCell>118                  <TableCell className={numCell}>{formatNumber(p.attempts)}</TableCell>119                  <TableCell className={numCell}>{formatNumber(p.successes)}</TableCell>120                  <TableCell className={numCell}>{formatPercent(p.attempts ? (p.successes / p.attempts) * 100 : null)}</TableCell>121                  <TableCell className={numCell}>{formatUsd(p.cost, true)}</TableCell>122                  <TableCell className={numCell}>{formatUsd(p.attributedRevenue, true)}</TableCell>123                  <TableCell className="text-right">124                    <MarginText value={p.attributedRevenue - p.cost} pct={p.attributedRevenue > 0 ? ((p.attributedRevenue - p.cost) / p.attributedRevenue) * 100 : null} />125                  </TableCell>126                  <TableCell className={`${numCell} font-semibold`}>{formatUsd(p.costPerSuccess, true)}</TableCell>127                  <TableCell className={numCell}>{formatBytes(p.bytes)}</TableCell>128                </TableRow>129              ))130            )}131          </TableBody>132        </Table>133      </Panel>134135      <Panel title="Top 20 customers" description="Ordered by revenue in the selected range." flush className="mt-6">136        <Table>137          <TableHeader>138            <TableRow>139              <TableHead>Organization</TableHead>140              <TableHead>Plan</TableHead>141              <TableHead className="text-right">Requests</TableHead>142              <TableHead className="text-right">Success</TableHead>143              <TableHead className="text-right">Revenue</TableHead>144              <TableHead className="text-right">Upstream</TableHead>145              <TableHead className="text-right">Margin</TableHead>146              <TableHead className="text-right">GB</TableHead>147            </TableRow>148          </TableHeader>149          <TableBody>150            {e.topCustomers.length === 0 ? (151              <TableEmpty colSpan={8}>No customer activity in this range.</TableEmpty>152            ) : (153              e.topCustomers.map((c) => (154                <TableRow key={c.key}>155                  <TableCell>156                    <Link href={`/admin/organizations/${c.key}`} className="font-medium hover:underline">157                      {c.name}158                    </Link>159                  </TableCell>160                  <TableCell>161                    <PlanBadge plan={c.plan} />162                  </TableCell>163                  <TableCell className={numCell}>{formatNumber(c.requests)}</TableCell>164                  <TableCell className={numCell}>{formatPercent(c.requests ? (c.successes / c.requests) * 100 : null)}</TableCell>165                  <TableCell className={numCell}>{formatUsd(c.revenue, true)}</TableCell>166                  <TableCell className={numCell}>{formatUsd(c.cost, true)}</TableCell>167                  <TableCell className="text-right">168                    <MarginText value={c.margin} pct={c.marginPct} />169                  </TableCell>170                  <TableCell className={numCell}>{formatBytes(c.bytes)}</TableCell>171                </TableRow>172              ))173            )}174          </TableBody>175        </Table>176      </Panel>177    </>178  );179}180