Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 1 | import type { ReactNode } from "react"; |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 2 | import { Link } from "react-router"; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3 | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 4 | import type { CostsReport } from "@g1t/contracts"; |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 5 | |
| 6 | import type { Route } from "./+types/costs"; | |
| 7 | import { DaysChart } from "~/components/costs"; | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 8 | import { CostsHeader, chip, costsHref } from "~/components/costs-header"; |
| 9 | import { Badge, Button, Field, Input, Notice, Section, Stat, When } from "~/components/ui"; | |
| 10 | import { capPercent, daySeries, marginTone, parseBucket, percentLabel, spendRows } from "~/lib/costs"; | |
| 11 | import { type CostsActionResult, costsAction, costsLoader } from "~/lib/costs-route.server"; | |
| 12 | import { usd } from "~/lib/money"; | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 13 | |
| 14 | export const meta: Route.MetaFunction = () => [{ title: "Costs & margin · sudo" }, { name: "robots", content: "noindex, nofollow" }]; | |
| 15 | ||
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 16 | export const loader = ({ request, context }: Route.LoaderArgs) => costsLoader(request, context); |
| 17 | export const action = ({ request, context }: Route.ActionArgs) => costsAction(request, context); | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 18 | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 19 | /** |
| 20 | * Where g1t's money goes: the statement, what g1t pays for itself against | |
| 21 | * its caps, and the same by day, by product and by workspace. | |
| 22 | */ | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 23 | export default function Costs({ loaderData, actionData }: Route.ComponentProps) { |
| 24 | const { range, report, error, done } = loaderData; | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 25 | const failed = actionData && "error" in actionData ? (actionData as CostsActionResult) : null; |
| 26 | const description = | |
| 27 | "What g1t cost to run (Cloudflare's bill and the model providers') against what workspaces paid, with what g1t gave away on purpose kept apart. Prices are cost plus 20%; the bill itself, drift and the price book are on Bill & pricing."; | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 28 | if (!report) { |
| 29 | return ( | |
| 30 | <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10"> | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 31 | <CostsHeader page="costs" range={range} report={null} done={null} runError={null} description={description} /> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 32 | <div className="mt-5"> |
| 33 | <Notice tone="warn">Billing did not answer for costs: {error}</Notice> | |
| 34 | </div> | |
| 35 | </main> | |
| 36 | ); | |
| 37 | } | |
| 38 | const bucket = parseBucket(loaderData.bucket, report.products.map((p) => p.bucket)); | |
| 39 | const product = report.products.find((p) => p.bucket === bucket) ?? null; | |
| 40 | const series = daySeries(report.days, report.since, report.until, bucket); | |
| 41 | const floor = report.settings.marginFloorPercent; | |
| 42 | const open = report.proposals.filter((p) => p.status === "open"); | |
| 43 | return ( | |
| 44 | <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10"> | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 45 | <CostsHeader page="costs" range={range} report={report} done={done} runError={failed?.section === "run" ? failed.error : null} description={description} /> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 46 | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 47 | <Statement report={report} floor={floor} range={range} proposals={open.length} /> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 48 | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 49 | <SpendSection caps={report.caps} error={failed?.section === "lift" ? failed.error : null} /> |
| 50 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 51 | <Section |
| 52 | className="mt-6" | |
| 53 | title={product ? `${product.title}, by day` : "By day"} | |
| 54 | description={ | |
| 55 | product | |
| 56 | ? "What customers were charged for it at price, before included usage and pools paid part, against what it cost." | |
| 57 | : "Money in (usage paid for and the plan) against every cost. Pick a product to see its own." | |
| 58 | } | |
| 59 | > | |
| 60 | <nav aria-label="Product" className="mb-4 flex flex-wrap gap-2"> | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 61 | <Link to={costsHref("costs", range, null)} aria-current={!bucket ? "page" : undefined} className={chip(!bucket)}> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 62 | All of g1t |
| 63 | </Link> | |
| 64 | {report.products.map((p) => ( | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 65 | <Link key={p.bucket} to={costsHref("costs", range, p.bucket)} aria-current={p.bucket === bucket ? "page" : undefined} className={chip(p.bucket === bucket)}> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 66 | {p.title} |
| 67 | </Link> | |
| 68 | ))} | |
| 69 | </nav> | |
| 70 | <DaysChart | |
| 71 | days={series} | |
| 72 | floor={floor} | |
| 73 | revenueLabel={product ? "Charged at price" : "Money in"} | |
| 74 | label={product ? `${product.title}: charged against Cloudflare cost by day` : "Money in against Cloudflare cost by day"} | |
| 75 | /> | |
| 76 | </Section> | |
| 77 | ||
| 78 | <Section className="mt-6" title="By product" description={`Each of g1t's products over the last ${range} days. The floor is ${floor}%.`}> | |
| 79 | <div className="-mx-4 overflow-x-auto sm:-mx-5"> | |
| 80 | <table className="w-full min-w-[44rem] text-sm"> | |
| 81 | <thead> | |
| 82 | <tr className="border-b border-line text-left text-xs text-muted"> | |
| 83 | <th className="px-4 py-2 font-medium sm:px-5">Product</th> | |
| 84 | <th className="px-4 py-2 text-right font-medium">Charged at price</th> | |
| 85 | <th className="px-4 py-2 text-right font-medium">Cost</th> | |
| 86 | <th className="px-4 py-2 text-right font-medium">Price book said</th> | |
| 87 | <th className="px-4 py-2 text-right font-medium">Margin</th> | |
| 88 | <th className="px-4 py-2 text-right font-medium sm:pr-5">%</th> | |
| 89 | </tr> | |
| 90 | </thead> | |
| 91 | <tbody> | |
| 92 | {report.products.map((p) => { | |
| 93 | const tone = p.overhead ? undefined : marginTone(p.marginPercent, floor); | |
| 94 | return ( | |
| 95 | <tr key={p.bucket} className="border-b border-line last:border-0"> | |
| 96 | <td className="px-4 py-2.5 sm:px-5"> | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 97 | <Link to={costsHref("costs", range, p.bucket)} className="text-fg hover:underline"> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 98 | {p.title} |
| 99 | </Link> | |
| 100 | <span className="block text-xs text-faint"> | |
| 101 | {p.overhead ? "Paid by the plan" : p.costSource === "ledger" ? "Cost from the gateway, not Cloudflare" : p.bucket} | |
| 102 | </span> | |
| 103 | </td> | |
| 104 | <td className="tabular px-4 py-2.5 text-right">{usd(p.valueMicros)}</td> | |
| 105 | <td className="tabular px-4 py-2.5 text-right">{usd(p.costMicros)}</td> | |
| 106 | <td className="tabular px-4 py-2.5 text-right text-muted">{usd(p.ownCostMicros)}</td> | |
| 107 | <td className={`tabular px-4 py-2.5 text-right ${p.marginMicros < 0 && !p.overhead ? "text-danger" : ""}`}>{usd(p.marginMicros)}</td> | |
| 108 | <td className="px-4 py-2.5 text-right sm:pr-5"> | |
| 109 | {p.overhead ? ( | |
| 110 | <span className="text-faint">—</span> | |
| 111 | ) : ( | |
| 112 | <Badge tone={tone === "mint" ? "mint" : tone === "warn" ? "warn" : tone === "danger" ? "danger" : "plain"}>{percentLabel(p.marginPercent)}</Badge> | |
| 113 | )} | |
| 114 | </td> | |
| 115 | </tr> | |
| 116 | ); | |
| 117 | })} | |
| 118 | </tbody> | |
| 119 | </table> | |
| 120 | </div> | |
| 121 | </Section> | |
| 122 | ||
| 123 | <Section | |
| 124 | className="mt-6" | |
| 125 | title="Workspaces that cost most" | |
| 126 | description="Cloudflare's costs shared out by each workspace's own meters (its git operations, its usage), against what it paid. Over its revenue for 30 days, a workspace shows on Reach out." | |
| 127 | > | |
| 128 | {report.topWorkspaces.length === 0 ? ( | |
| 129 | <p className="text-sm text-muted">No costs shared out yet.</p> | |
| 130 | ) : ( | |
| 131 | <div className="-mx-4 overflow-x-auto sm:-mx-5"> | |
| 132 | <table className="w-full min-w-[34rem] text-sm"> | |
| 133 | <thead> | |
| 134 | <tr className="border-b border-line text-left text-xs text-muted"> | |
| 135 | <th className="px-4 py-2 font-medium sm:px-5">Workspace</th> | |
| 136 | <th className="px-4 py-2 text-right font-medium">Cost to g1t</th> | |
| Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it | 137 | <th className="px-4 py-2 text-right font-medium">Given away</th> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 138 | <th className="px-4 py-2 text-right font-medium">Paid</th> |
| 139 | <th className="px-4 py-2 text-right font-medium sm:pr-5">Net</th> | |
| 140 | </tr> | |
| 141 | </thead> | |
| 142 | <tbody> | |
| 143 | {report.topWorkspaces.map((w) => { | |
| 144 | const net = w.revenueMicros - w.costMicros; | |
| 145 | return ( | |
| 146 | <tr key={w.workspace} className="border-b border-line last:border-0"> | |
| 147 | <td className="px-4 py-2.5 sm:px-5"> | |
| 148 | <Link to={`/workspaces/${encodeURIComponent(w.workspace)}#billing`} className="font-mono text-fg hover:underline"> | |
| 149 | {w.workspace} | |
| 150 | </Link> | |
| 151 | {w.internal && <span className="ml-2 text-xs text-faint">g1t's own</span>} | |
| 152 | </td> | |
| 153 | <td className="tabular px-4 py-2.5 text-right">{usd(w.costMicros)}</td> | |
| Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it | 154 | <td className="tabular px-4 py-2.5 text-right text-fg-soft">{usd(w.givenMicros ?? 0)}</td> |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 155 | <td className="tabular px-4 py-2.5 text-right">{usd(w.revenueMicros)}</td> |
| 156 | <td className={`tabular px-4 py-2.5 text-right sm:pr-5 ${net < 0 && !w.internal ? "text-danger" : "text-fg-soft"}`}>{usd(net)}</td> | |
| 157 | </tr> | |
| 158 | ); | |
| 159 | })} | |
| 160 | </tbody> | |
| 161 | </table> | |
| 162 | </div> | |
| 163 | )} | |
| 164 | </Section> | |
| 165 | ||
| 166 | </main> | |
| 167 | ); | |
| 168 | } | |
| 169 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 170 | function CapMeter({ label, used, cap, hint }: { label: string; used: number; cap: number; hint: ReactNode }) { |
| 171 | const percent = capPercent(used, cap); | |
| 172 | const tone = cap > 0 && used >= cap ? "bg-danger" : percent >= 75 ? "bg-warn" : "bg-merged"; | |
| 173 | return ( | |
| 174 | <div className="rounded-md border border-line px-4 py-3"> | |
| 175 | <div className="flex flex-wrap items-baseline justify-between gap-2"> | |
| 176 | <span className="text-sm font-medium">{label}</span> | |
| 177 | <span className="tabular text-sm"> | |
| 178 | {usd(used)} <span className="text-faint">of {cap > 0 ? usd(cap) : "no cap"}</span> | |
| 179 | </span> | |
| 180 | </div> | |
| 181 | <div className="mt-2 h-1.5 overflow-hidden rounded-full bg-line" role="meter" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(percent)} aria-label={label}> | |
| 182 | <div className={`h-full ${tone}`} style={{ width: `${percent}%` }} /> | |
| 183 | </div> | |
| 184 | <p className="mt-1.5 text-xs text-muted">{hint}</p> | |
| 185 | </div> | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 186 | ); |
| 187 | } | |
| 188 | ||
| 189 | /** | |
| 190 | * Where the money went, as a short statement: usage sold and running g1t | |
| 191 | * (each against what paid for it), what g1t gave away on purpose (a budget, | |
| 192 | * never a loss), and who was paid. | |
| 193 | */ | |
| 194 | function Statement({ report, floor, range, proposals }: { report: CostsReport; floor: number; range: number; proposals: number }) { | |
| 195 | const o = report.overall; | |
| 196 | // Reports from before the statement have only the totals. | |
| 197 | const given = o.givenMicros ?? 0; | |
| 198 | const usageCost = o.usageCostMicros ?? o.costMicros - given; | |
| Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript) | 199 | const included = o.includedMicros ?? 0; |
| 200 | const usageIn = o.usageMicros + included; | |
| 201 | const usageMargin = o.usageMarginMicros ?? usageIn - usageCost; | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 202 | const usagePercent = o.usageMarginPercent !== undefined ? o.usageMarginPercent : null; |
| 203 | const running = o.runningCostMicros ?? 0; | |
| 204 | const unmapped = o.unmappedCostMicros ?? 0; | |
| 205 | // Cloudflare's subscriptions are not on the usage bill: the estimate, over the range. | |
| 206 | const subscriptions = Math.round((report.caps.fixedMonthlyMicros * range) / 30); | |
| 207 | const moneyIn = o.usageMicros + o.plansMicros; | |
| 208 | const spent = o.costMicros + subscriptions; | |
| 209 | const net = moneyIn - spent; | |
| 210 | const givenParts = [ | |
| 211 | ["comped", o.givenCompedMicros ?? 0], | |
| 212 | ["free use", o.givenFreeMicros ?? 0], | |
| 213 | ["trial", o.givenTrialMicros ?? 0], | |
| 214 | ["open-source pool", o.givenPoolMicros ?? 0], | |
| 215 | ].filter(([, micros]) => (micros as number) > 0) as [string, number][]; | |
| 216 | const rows: { title: string; note: string; in: number | null; cost: number; result: number | null; tone?: "danger" | "warn" | "muted" }[] = [ | |
| 217 | { | |
| 218 | title: "Usage sold", | |
| Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript) | 219 | note: |
| 220 | included > 0 | |
| sudo: the costs statement reads to the cent | 221 | ? `What workspaces paid for usage (${usd(o.usageMicros, { cents: true })}) and their plan's included usage (${usd(included, { cents: true })}), against what it cost` |
| Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript) | 222 | : "What workspaces paid for usage, against what that usage cost", |
| 223 | in: usageIn, | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 224 | cost: usageCost, |
| 225 | result: usageMargin, | |
| 226 | tone: usageMargin < 0 ? "danger" : undefined, | |
| 227 | }, | |
| 228 | { | |
| 229 | title: "Running g1t", | |
| Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript) | 230 | note: |
| 231 | included > 0 | |
| sudo: the costs statement reads to the cent | 232 | ? `Plans (${usd(o.plansMicros, { cents: true })}) less the included usage they paid for, against Workers, D1, KV, Queues and the rest` |
| Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript) | 233 | : "Plans, against Workers, D1, KV, Queues and the rest of the platform", |
| 234 | in: o.plansMicros - included, | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 235 | cost: running, |
| Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript) | 236 | result: o.plansMicros - included - running, |
| 237 | tone: o.plansMicros - included - running < 0 ? "warn" : undefined, | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 238 | }, |
| 239 | { | |
| 240 | title: "Cloudflare subscriptions", | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 241 | note: |
| 242 | report.caps.fixedSource === "cloudflare" | |
| 243 | ? `Fixed, as Cloudflare lists them${(report.caps.fixedItems ?? []).length > 0 ? ` (${(report.caps.fixedItems ?? []).map((i) => i.name).join(", ")})` : ""}; not on the usage bill` | |
| 244 | : "Fixed, an estimate (CLOUDFLARE_FIXED_MONTHLY_MICROS) until Cloudflare's list is read; not on the usage bill", | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 245 | in: null, |
| 246 | cost: subscriptions, | |
| 247 | result: -subscriptions, | |
| 248 | tone: "muted", | |
| 249 | }, | |
| 250 | ...(unmapped > 0 | |
| 251 | ? [{ title: "Not mapped", note: "Billed by Cloudflare, charged for by nothing yet", in: null, cost: unmapped, result: -unmapped, tone: "danger" as const }] | |
| 252 | : []), | |
| 253 | { | |
| 254 | title: "Given away", | |
| sudo: the costs statement reads to the cent | 255 | note: givenParts.length > 0 ? givenParts.map(([why, micros]) => `${why} ${usd(micros, { cents: true })}`).join(" · ") : "Nothing this range", |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 256 | in: null, |
| 257 | cost: given, | |
| 258 | result: null, | |
| 259 | tone: "muted", | |
| 260 | }, | |
| 261 | ]; | |
| 262 | const resultClass = (tone?: string) => (tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "muted" ? "text-muted" : "text-fg"); | |
| 263 | return ( | |
| 264 | <> | |
| 265 | <div className="mt-5 grid grid-cols-2 gap-3 lg:grid-cols-4"> | |
| 266 | <Stat | |
| 267 | label="Margin on usage sold" | |
| 268 | value={percentLabel(usagePercent)} | |
| sudo: the costs statement reads to the cent | 269 | hint={`${usd(usageIn, { cents: true })} paid for usage that cost ${usd(usageCost, { cents: true })}`} |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 270 | tone={marginTone(usagePercent, floor)} |
| 271 | /> | |
| 272 | <Stat | |
| 273 | label="Given away on purpose" | |
| sudo: the costs statement reads to the cent | 274 | value={usd(given, { cents: true })} |
| 275 | hint={givenParts.length > 0 ? givenParts.map(([why, micros]) => `${usd(micros, { cents: true })} ${why}`).join(", ") : "Nothing given this range"} | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 276 | /> |
| 277 | <Stat | |
| 278 | label="Who g1t paid" | |
| sudo: the costs statement reads to the cent | 279 | value={usd(o.costMicros, { cents: true })} |
| 280 | hint={`Cloudflare ${usd(o.cloudflareCostMicros ?? o.costMicros - (o.modelsCostMicros ?? 0), { cents: true })}, model providers ${usd(o.modelsCostMicros ?? 0, { cents: true })}`} | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 281 | /> |
| 282 | <Stat | |
| 283 | label="Proposals waiting" | |
| 284 | value={String(proposals)} | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 285 | hint={ |
| 286 | <> | |
| 287 | <Link to={costsHref("bill", range) + "#proposals"} className="underline underline-offset-2"> | |
| 288 | On Bill & pricing | |
| 289 | </Link> | |
| 290 | {report.fetchedAt ? ( | |
| 291 | <> | |
| 292 | {" "} | |
| 293 | · bill read <When at={report.fetchedAt} time /> | |
| 294 | </> | |
| 295 | ) : ( | |
| 296 | " · the bill has not been read yet" | |
| 297 | )} | |
| 298 | </> | |
| 299 | } | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 300 | tone={proposals > 0 ? "warn" : undefined} |
| 301 | /> | |
| 302 | </div> | |
| 303 | <div className="mt-3 overflow-x-auto rounded-lg border border-line bg-surface"> | |
| 304 | <table className="w-full min-w-[36rem] text-sm"> | |
| 305 | <thead> | |
| 306 | <tr className="border-b border-line text-left text-xs text-muted"> | |
| 307 | <th className="px-4 py-2 font-medium sm:px-5"> | |
| 308 | {report.since} to {report.until} | |
| 309 | </th> | |
| 310 | <th className="px-4 py-2 text-right font-medium">Paid in</th> | |
| 311 | <th className="px-4 py-2 text-right font-medium">Cost</th> | |
| 312 | <th className="px-4 py-2 text-right font-medium sm:pr-5">Result</th> | |
| 313 | </tr> | |
| 314 | </thead> | |
| 315 | <tbody> | |
| 316 | {rows.map((row) => ( | |
| 317 | <tr key={row.title} className="border-b border-line"> | |
| 318 | <td className="px-4 py-2.5 sm:px-5"> | |
| 319 | <span className="text-fg">{row.title}</span> | |
| 320 | <span className="block text-xs text-faint">{row.note}</span> | |
| 321 | </td> | |
| sudo: the costs statement reads to the cent | 322 | <td className="tabular px-4 py-2.5 text-right">{row.in == null ? <span className="text-faint">—</span> : usd(row.in, { cents: true })}</td> |
| 323 | <td className="tabular px-4 py-2.5 text-right">{usd(row.cost, { cents: true })}</td> | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 324 | <td className={`tabular px-4 py-2.5 text-right sm:pr-5 ${resultClass(row.tone)}`}> |
| sudo: the costs statement reads to the cent | 325 | {row.result == null ? "a budget" : usd(row.result, { signed: true, cents: true })} |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 326 | </td> |
| 327 | </tr> | |
| 328 | ))} | |
| 329 | <tr> | |
| 330 | <td className="px-4 py-2.5 sm:px-5"> | |
| 331 | <span className="font-medium text-fg">All in</span> | |
| 332 | <span className="block text-xs text-faint"> | |
| sudo: the costs statement reads to the cent | 333 | {given > 0 ? `${usd(given, { cents: true })} of the cost was given away on purpose; without it, ${usd(net + given, { signed: true, cents: true })}` : "Everything above"} |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 334 | </span> |
| 335 | </td> | |
| sudo: the costs statement reads to the cent | 336 | <td className="tabular px-4 py-2.5 text-right font-medium">{usd(moneyIn, { cents: true })}</td> |
| 337 | <td className="tabular px-4 py-2.5 text-right font-medium">{usd(spent, { cents: true })}</td> | |
| 338 | <td className="tabular px-4 py-2.5 text-right font-medium text-fg sm:pr-5">{usd(net, { signed: true, cents: true })}</td> | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 339 | </tr> |
| 340 | </tbody> | |
| 341 | </table> | |
| 342 | </div> | |
| 343 | </> | |
| 344 | ); | |
| 345 | } | |
| 346 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 347 | function SpendSection({ caps, error }: { caps: CostsReport["caps"]; error: string | null }) { |
| 348 | const { rows, totalMicros } = spendRows(caps); | |
| 349 | const net = caps.revenueMicros - totalMicros; | |
| 350 | return ( | |
| 351 | <Section | |
| 352 | className="mt-6" | |
| 353 | id="spend" | |
| 354 | title="g1t's own spend" | |
| 355 | description="What g1t pays for itself, at cost: comped accounts, the trial and open-source pools, free workspaces' overruns, and anything charged without real money behind it. Two caps hold it: each comped account's monthly budget, and a daily breaker on all of it that pauses new hosted-model agent runs g1t would pay for." | |
| 356 | > | |
| 357 | <div className="grid gap-3 lg:grid-cols-2"> | |
| 358 | <CapMeter | |
| 359 | label={`Today, ${caps.day} (UTC)`} | |
| 360 | used={caps.todayMicros} | |
| 361 | cap={caps.dailyCapMicros} | |
| 362 | hint={ | |
| 363 | caps.tripped ? ( | |
| 364 | <span className="text-danger"> | |
| 365 | Breaker open{caps.trippedAt ? <> since <When at={caps.trippedAt} time /></> : null}: new hosted-model runs g1t pays for wait until 00:00 UTC. | |
| 366 | </span> | |
| 367 | ) : caps.liftedBy ? ( | |
| 368 | <> | |
| 369 | Lifted for today by {caps.liftedBy} | |
| 370 | {caps.liftNote ? `: “${caps.liftNote}”` : ""}. | |
| 371 | </> | |
| 372 | ) : ( | |
| 373 | "Paying workspaces on a live card are never paused. PLATFORM_DAILY_SPEND_CAP_MICROS." | |
| 374 | ) | |
| 375 | } | |
| 376 | /> | |
| 377 | {caps.comped.map((b) => ( | |
| 378 | <CapMeter | |
| 379 | key={b.account} | |
| 380 | label={`${b.name}, ${caps.month} (comped)`} | |
| 381 | used={b.usedMicros} | |
| 382 | cap={b.ceilingMicros} | |
| 383 | hint={ | |
| 384 | <> | |
| 385 | {b.ceilingMicros > 0 && b.usedMicros >= b.ceilingMicros ? ( | |
| 386 | <span className="text-danger">Used up: new work on it is refused. </span> | |
| 387 | ) : null} | |
| 388 | {b.defaultCeiling ? "The default budget (COMPED_MONTHLY_CEILING_MICROS)" : "Its own budget, in its terms"}. Raise it on{" "} | |
| 389 | <Link to={`/workspaces/${encodeURIComponent(b.name)}#billing`} className="underline underline-offset-2"> | |
| 390 | its account | |
| 391 | </Link> | |
| 392 | : Terms, Limit. | |
| 393 | </> | |
| 394 | } | |
| 395 | /> | |
| 396 | ))} | |
| 397 | </div> | |
| 398 | {(caps.tripped || error) && ( | |
| 399 | <form method="post" action="#spend" className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-end"> | |
| 400 | <input type="hidden" name="intent" value="lift" /> | |
| 401 | <Field label="Why lift it" hint="Recorded in the audit log."> | |
| 402 | <Input name="note" required minLength={5} maxLength={500} placeholder="e.g. Launch day; watching it" className="sm:min-w-[24rem]" /> | |
| 403 | </Field> | |
| 404 | <Button type="submit" variant="danger"> | |
| 405 | Lift for today | |
| 406 | </Button> | |
| 407 | </form> | |
| 408 | )} | |
| 409 | {error && ( | |
| 410 | <div className="mt-3"> | |
| 411 | <Notice tone="error">{error}</Notice> | |
| 412 | </div> | |
| 413 | )} | |
| 414 | <div className="-mx-4 mt-5 overflow-x-auto sm:-mx-5"> | |
| 415 | <table className="w-full min-w-[34rem] text-sm"> | |
| 416 | <thead> | |
| 417 | <tr className="border-b border-line text-left text-xs text-muted"> | |
| 418 | <th className="px-4 py-2 font-medium sm:px-5">{caps.month}, so far</th> | |
| 419 | <th className="px-4 py-2 text-right font-medium sm:pr-5">g1t paid</th> | |
| 420 | </tr> | |
| 421 | </thead> | |
| 422 | <tbody> | |
| 423 | {rows.map((row) => ( | |
| 424 | <tr key={row.key} className="border-b border-line align-top"> | |
| 425 | <td className="px-4 py-2.5 sm:px-5"> | |
| 426 | {row.title} | |
| 427 | <span className="block text-xs text-faint">{row.note}</span> | |
| 428 | </td> | |
| 429 | <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(row.micros)}</td> | |
| 430 | </tr> | |
| 431 | ))} | |
| 432 | <tr className="border-b border-line font-medium"> | |
| 433 | <td className="px-4 py-2.5 sm:px-5">All of it</td> | |
| 434 | <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(totalMicros)}</td> | |
| 435 | </tr> | |
| 436 | <tr className="border-b border-line"> | |
| 437 | <td className="px-4 py-2.5 sm:px-5"> | |
| 438 | Money in | |
| 439 | <span className="block text-xs text-faint">Usage paid for and the plan, reconciled through yesterday</span> | |
| 440 | </td> | |
| 441 | <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(caps.revenueMicros)}</td> | |
| 442 | </tr> | |
| 443 | <tr> | |
| 444 | <td className="px-4 py-2.5 sm:px-5">Money in less what g1t paid</td> | |
| 445 | <td className={`tabular px-4 py-2.5 text-right sm:pr-5 ${net < 0 ? "text-danger" : "text-fg-soft"}`}>{usd(net, { signed: true })}</td> | |
| 446 | </tr> | |
| 447 | </tbody> | |
| 448 | </table> | |
| 449 | </div> | |
| 450 | </Section> | |
| 451 | ); | |
| 452 | } | |
| 453 |