Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

684 linesCodeBlameRaw
1import type { ReactNode } from "react";
2import { Link } from "react-router";
3
4import type { CostsReport, PlatformGuard, PlatformMetric } from "@g1t/contracts";
5
6import type { Route } from "./+types/costs";
7import { DaysChart } from "~/components/costs";
8import { CostsHeader, chip, costsHref } from "~/components/costs-header";
9import { Badge, Button, Field, Input, Notice, Section, Stat, When } from "~/components/ui";
10import { PAUSE_LEVELS, capPercent, count, daySeries, givenParts as givenBy, marginOnPrice, marginTone, parseBucket, percentLabel, spendRows, subscriptionsOver, thresholdShare, whoPaid } from "~/lib/costs";
11import { type CostsActionResult, costsAction, costsLoader } from "~/lib/costs-route.server";
12import { usd } from "~/lib/money";
13
14export const meta: Route.MetaFunction = () => [{ title: "Costs & margin · sudo" }, { name: "robots", content: "noindex, nofollow" }];
15
16/** Billing's MARGIN_PERCENT: what is added to cost. */
17const MARKUP_PERCENT = 20;
18
19export const loader = ({ request, context }: Route.LoaderArgs) => costsLoader(request, context);
20export const action = ({ request, context }: Route.ActionArgs) => costsAction(request, context);
21
22/**
23 * Where g1t's money goes: the statement, what g1t pays for itself against
24 * its caps, and the same by day, by product and by workspace.
25 */
26export default function Costs({ loaderData, actionData }: Route.ComponentProps) {
27 const { range, report, error, done } = loaderData;
28 const failed = actionData && "error" in actionData ? (actionData as CostsActionResult) : null;
29 const description = `What g1t cost to run (Cloudflare's bill) against what workspaces paid, with what g1t gave away on purpose kept apart. Prices are cost plus ${MARKUP_PERCENT}%, a ${percentLabel(marginOnPrice(MARKUP_PERCENT))} margin: every margin here is a share of the price, not of the cost. The bill itself, drift and the price book are on Bill & pricing.`;
30 if (!report) {
31 return (
32 <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10">
33 <CostsHeader page="costs" range={range} report={null} done={null} runError={null} description={description} />
34 <div className="mt-5">
35 <Notice tone="warn">Billing did not answer for costs: {error}</Notice>
36 </div>
37 <PlatformSection guard={loaderData.guard} unavailable={loaderData.guardError} failed={failed} />
38 </main>
39 );
40 }
41 const bucket = parseBucket(loaderData.bucket, report.products.map((p) => p.bucket));
42 const product = report.products.find((p) => p.bucket === bucket) ?? null;
43 const series = daySeries(report.days, report.since, report.until, bucket);
44 const floor = report.settings.marginFloorPercent;
45 const open = report.proposals.filter((p) => p.status === "open");
46 return (
47 <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10">
48 <CostsHeader page="costs" range={range} report={report} done={done} runError={failed?.section === "run" ? failed.error : null} description={description} />
49
50 <Statement report={report} floor={floor} range={range} proposals={open.length} />
51
52 <SpendSection caps={report.caps} range={range} error={failed?.section === "lift" ? failed.error : null} />
53
54 <PlatformSection guard={loaderData.guard} unavailable={loaderData.guardError} failed={failed} />
55
56 <Section
57 className="mt-6"
58 title={product ? `${product.title}, by day` : "By day"}
59 description={
60 product
61 ? "What customers were charged for it at price, before included usage and pools paid part, against what it cost."
62 : "Money in (usage paid for and the plan) against every cost. Pick a product to see its own."
63 }
64 >
65 <nav aria-label="Product" className="mb-4 flex flex-wrap gap-2">
66 <Link to={costsHref("costs", range, null)} aria-current={!bucket ? "page" : undefined} className={chip(!bucket)}>
67 All of g1t
68 </Link>
69 {report.products.map((p) => (
70 <Link key={p.bucket} to={costsHref("costs", range, p.bucket)} aria-current={p.bucket === bucket ? "page" : undefined} className={chip(p.bucket === bucket)}>
71 {p.title}
72 </Link>
73 ))}
74 </nav>
75 <DaysChart
76 days={series}
77 floor={floor}
78 revenueLabel={product ? "Charged at price" : "Money in"}
79 label={product ? `${product.title}: charged against Cloudflare cost by day` : "Money in against Cloudflare cost by day"}
80 />
81 </Section>
82
83 <Section
84 className="mt-6"
85 title="By product"
86 description={`Each of g1t's products over the last ${range} days: what customers were charged at price, given away or not, against what it cost. The margin is the charge less the cost, as a share of the charge; at cost plus ${MARKUP_PERCENT}% it is ${percentLabel(marginOnPrice(MARKUP_PERCENT))}. The floor is ${floor}%.`}
87 >
88 <div className="-mx-4 overflow-x-auto sm:-mx-5">
89 <table className="w-full min-w-176 text-sm">
90 <thead>
91 <tr className="border-b border-line text-left text-xs text-muted">
92 <th className="px-4 py-2 font-medium sm:px-5">Product</th>
93 <th className="px-4 py-2 text-right font-medium">Charged at price</th>
94 <th className="px-4 py-2 text-right font-medium">Cost</th>
95 <th className="px-4 py-2 text-right font-medium">Price book said</th>
96 <th className="px-4 py-2 text-right font-medium">Margin</th>
97 <th className="px-4 py-2 text-right font-medium sm:pr-5">% of price</th>
98 </tr>
99 </thead>
100 <tbody>
101 {report.products.map((p) => {
102 const tone = p.overhead ? undefined : marginTone(p.marginPercent, floor);
103 return (
104 <tr key={p.bucket} className="border-b border-line last:border-0">
105 <td className="px-4 py-2.5 sm:px-5">
106 <Link to={costsHref("costs", range, p.bucket)} className="text-fg hover:underline">
107 {p.title}
108 </Link>
109 <span className="block text-xs text-faint">
110 {p.overhead ? "Paid by the plan" : p.costSource === "ledger" ? "Cost from the gateway, not Cloudflare" : p.bucket}
111 </span>
112 </td>
113 <td className="tabular px-4 py-2.5 text-right">{usd(p.valueMicros)}</td>
114 <td className="tabular px-4 py-2.5 text-right">{usd(p.costMicros)}</td>
115 <td className="tabular px-4 py-2.5 text-right text-muted">{usd(p.ownCostMicros)}</td>
116 <td className={`tabular px-4 py-2.5 text-right ${p.marginMicros < 0 && !p.overhead ? "text-danger" : ""}`}>{usd(p.marginMicros)}</td>
117 <td className="px-4 py-2.5 text-right sm:pr-5">
118 {p.overhead ? (
119 <span className="text-faint">—</span>
120 ) : (
121 <Badge tone={tone === "mint" ? "mint" : tone === "warn" ? "warn" : tone === "danger" ? "danger" : "plain"}>{percentLabel(p.marginPercent)}</Badge>
122 )}
123 </td>
124 </tr>
125 );
126 })}
127 </tbody>
128 </table>
129 </div>
130 </Section>
131
132 <Section
133 className="mt-6"
134 title="Workspaces that cost most"
135 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."
136 >
137 {report.topWorkspaces.length === 0 ? (
138 <p className="text-sm text-muted">No costs shared out yet.</p>
139 ) : (
140 <div className="-mx-4 overflow-x-auto sm:-mx-5">
141 <table className="w-full min-w-136 text-sm">
142 <thead>
143 <tr className="border-b border-line text-left text-xs text-muted">
144 <th className="px-4 py-2 font-medium sm:px-5">Workspace</th>
145 <th className="px-4 py-2 text-right font-medium">Cost to g1t</th>
146 <th className="px-4 py-2 text-right font-medium">Given away</th>
147 <th className="px-4 py-2 text-right font-medium">Paid</th>
148 <th className="px-4 py-2 text-right font-medium sm:pr-5">Net</th>
149 </tr>
150 </thead>
151 <tbody>
152 {report.topWorkspaces.map((w) => {
153 const net = w.revenueMicros - w.costMicros;
154 // What was given away on purpose is a budget, not a loss:
155 // red only for what neither paid nor was given.
156 const lost = net + (w.givenMicros ?? 0) < 0;
157 return (
158 <tr key={w.workspace} className="border-b border-line last:border-0">
159 <td className="px-4 py-2.5 sm:px-5">
160 <Link to={`/workspaces/${encodeURIComponent(w.workspace)}#billing`} className="font-mono text-fg hover:underline">
161 {w.workspace}
162 </Link>
163 {w.internal && <span className="ml-2 text-xs text-faint">g1t's own</span>}
164 </td>
165 <td className="tabular px-4 py-2.5 text-right">{usd(w.costMicros)}</td>
166 <td className="tabular px-4 py-2.5 text-right text-fg-soft">{usd(w.givenMicros ?? 0)}</td>
167 <td className="tabular px-4 py-2.5 text-right">{usd(w.revenueMicros)}</td>
168 <td className={`tabular px-4 py-2.5 text-right sm:pr-5 ${lost && !w.internal ? "text-danger" : "text-fg-soft"}`}>{usd(net)}</td>
169 </tr>
170 );
171 })}
172 </tbody>
173 </table>
174 </div>
175 )}
176 {(report.unattributedMicros ?? 0) > 0 && (
177 <p className="mt-3 text-xs text-muted">
178 And {usd(report.unattributedMicros ?? 0, { cents: true })} of running g1t on days no workspace used anything: no one&apos;s, so not above. The
179 workspaces&apos; costs and this add up to the cost in the statement.
180 </p>
181 )}
182 </Section>
183
184 </main>
185 );
186}
187
188function CapMeter({ label, used, cap, hint }: { label: string; used: number; cap: number; hint: ReactNode }) {
189 const percent = capPercent(used, cap);
190 const tone = cap > 0 && used >= cap ? "fill-danger" : percent >= 75 ? "fill-warn" : "fill-accent";
191 return (
192 <div className="rounded-md border border-line px-4 py-3">
193 <div className="flex flex-wrap items-baseline justify-between gap-2">
194 <span className="text-sm font-medium">{label}</span>
195 <span className="tabular text-sm">
196 {usd(used)} <span className="text-faint">of {cap > 0 ? usd(cap) : "no cap"}</span>
197 </span>
198 </div>
199 {/* Drawn as SVG, its width an attribute: the page's style policy blocks a style attribute. */}
200 <svg viewBox="0 0 100 4" preserveAspectRatio="none" className="mt-2 block h-1.5 w-full" role="meter" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(percent)} aria-label={label}>
201 <rect x="0" y="0" width="100" height="4" rx="2" className="fill-line" />
202 {percent > 0 && <rect x="0" y="0" width={Math.max(2, percent)} height="4" rx="2" className={tone} />}
203 </svg>
204 <p className="mt-1.5 text-xs text-muted">{hint}</p>
205 </div>
206 );
207}
208
209/**
210 * Where the money went, as a short statement: usage sold and running g1t
211 * (each against what paid for it), what g1t gave away on purpose (a budget,
212 * never a loss), and who was paid.
213 */
214function Statement({ report, floor, range, proposals }: { report: CostsReport; floor: number; range: number; proposals: number }) {
215 const o = report.overall;
216 // Reports from before the statement have only the totals.
217 const given = o.givenMicros ?? 0;
218 const usageCost = o.usageCostMicros ?? o.costMicros - given;
219 const included = o.includedMicros ?? 0;
220 const usageIn = o.usageMicros + included;
221 const usageMargin = o.usageMarginMicros ?? usageIn - usageCost;
222 // Under a cent sold, a percentage says nothing (a few micros against none).
223 const soldSomething = usageIn >= 10_000;
224 const usagePercent = soldSomething && o.usageMarginPercent !== undefined ? o.usageMarginPercent : null;
225 const running = o.runningCostMicros ?? 0;
226 const unmapped = o.unmappedCostMicros ?? 0;
227 // Cloudflare's subscriptions are not on the usage bill: each day's share of its billing cycle.
228 const subscriptions = subscriptionsOver(report.caps.fixedMonthlyMicros, range, o);
229 const paid = whoPaid(o, subscriptions);
230 const moneyIn = o.usageMicros + o.plansMicros;
231 const spent = paid.totalMicros;
232 const net = moneyIn - spent;
233 const givenParts = givenBy(o);
234 const rows: { title: string; note: string; in: number | null; cost: number; result: number | null; tone?: "danger" | "warn" | "muted" }[] = [
235 {
236 title: "Usage sold",
237 note:
238 included > 0
239 ? `What workspaces paid for usage (${usd(o.usageMicros, { cents: true })}) and their plan's included usage (${usd(included, { cents: true })}), against what it cost`
240 : "What workspaces paid for usage, against what that usage cost",
241 in: usageIn,
242 cost: usageCost,
243 result: usageMargin,
244 tone: usageMargin < 0 ? "danger" : undefined,
245 },
246 {
247 title: "Running g1t",
248 note:
249 included > 0
250 ? `Plans (${usd(o.plansMicros, { cents: true })}) less the included usage they paid for, against Workers, D1, KV, Queues and the rest`
251 : "Plans, against Workers, D1, KV, Queues and the rest of the platform",
252 in: o.plansMicros - included,
253 cost: running,
254 result: o.plansMicros - included - running,
255 tone: o.plansMicros - included - running < 0 ? "warn" : undefined,
256 },
257 {
258 title: "Cloudflare subscriptions",
259 note:
260 report.caps.fixedSource === "cloudflare"
261 ? `Fixed, as Cloudflare lists them${(report.caps.fixedItems ?? []).length > 0 ? ` (${(report.caps.fixedItems ?? []).map((i) => i.name).join(", ")})` : ""}; not on the usage bill. Each day is its billing cycle's share of ${usd(report.caps.fixedMonthlyMicros, { cents: true })} a month`
262 : "Fixed, an estimate (CLOUDFLARE_FIXED_MONTHLY_MICROS) until Cloudflare's list is read; not on the usage bill",
263 in: null,
264 cost: subscriptions,
265 result: -subscriptions,
266 tone: "muted",
267 },
268 ...(unmapped > 0
269 ? [{ title: "Not mapped", note: "Billed by Cloudflare, charged for by nothing yet", in: null, cost: unmapped, result: -unmapped, tone: "danger" as const }]
270 : []),
271 {
272 title: "Given away",
273 note: givenParts.length > 0 ? givenParts.map(([why, micros]) => `${why} ${usd(micros, { cents: true })}`).join(" · ") : "Nothing this range",
274 in: null,
275 cost: given,
276 result: null,
277 tone: "muted",
278 },
279 ];
280 const resultClass = (tone?: string) => (tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "muted" ? "text-muted" : "text-fg");
281 return (
282 <>
283 <div className="mt-5 grid grid-cols-2 gap-3 lg:grid-cols-4">
284 <Stat
285 label="Margin on usage sold"
286 value={percentLabel(usagePercent)}
287 hint={
288 soldSomething
289 ? `Of the price: ${usd(usageIn, { cents: true })} paid for usage that cost ${usd(usageCost, { cents: true })}`
290 : "No usage sold in this range"
291 }
292 tone={marginTone(usagePercent, floor)}
293 />
294 <Stat
295 label="Given away on purpose"
296 value={usd(given, { cents: true })}
297 hint={givenParts.length > 0 ? givenParts.map(([why, micros]) => `${usd(micros, { cents: true })} ${why}`).join(", ") : "Nothing given this range"}
298 />
299 <Stat
300 label="Who g1t paid"
301 value={usd(paid.totalMicros, { cents: true })}
302 hint={`Cloudflare's usage ${usd(paid.cloudflareMicros, { cents: true })} and subscriptions ${usd(paid.subscriptionsMicros, { cents: true })}${paid.modelsMicros > 0 ? `, model providers ${usd(paid.modelsMicros, { cents: true })} (from before agents were retired)` : ""}; the cost in All in`}
303 />
304 <Stat
305 label="Proposals waiting"
306 value={String(proposals)}
307 hint={
308 <>
309 <Link to={costsHref("bill", range) + "#proposals"} className="underline underline-offset-2">
310 On Bill &amp; pricing
311 </Link>
312 {report.fetchedAt ? (
313 <>
314 {" "}
315 · bill read <When at={report.fetchedAt} time />
316 </>
317 ) : (
318 " · the bill has not been read yet"
319 )}
320 </>
321 }
322 tone={proposals > 0 ? "warn" : undefined}
323 />
324 </div>
325 <div className="mt-3 overflow-x-auto rounded-lg border border-line bg-surface">
326 <table className="w-full min-w-xl text-sm">
327 <thead>
328 <tr className="border-b border-line text-left text-xs text-muted">
329 <th className="px-4 py-2 font-medium sm:px-5">
330 {report.since} to {report.until}
331 </th>
332 <th className="px-4 py-2 text-right font-medium">Paid in</th>
333 <th className="px-4 py-2 text-right font-medium">Cost</th>
334 <th className="px-4 py-2 text-right font-medium sm:pr-5">Result</th>
335 </tr>
336 </thead>
337 <tbody>
338 {rows.map((row) => (
339 <tr key={row.title} className="border-b border-line">
340 <td className="px-4 py-2.5 sm:px-5">
341 <span className="text-fg">{row.title}</span>
342 <span className="block text-xs text-faint">{row.note}</span>
343 </td>
344 <td className="tabular px-4 py-2.5 text-right">{row.in == null ? <span className="text-faint">—</span> : usd(row.in, { cents: true })}</td>
345 <td className="tabular px-4 py-2.5 text-right">{usd(row.cost, { cents: true })}</td>
346 <td className={`tabular px-4 py-2.5 text-right sm:pr-5 ${resultClass(row.tone)}`}>
347 {row.result == null ? "a budget" : usd(row.result, { signed: true, cents: true })}
348 </td>
349 </tr>
350 ))}
351 <tr>
352 <td className="px-4 py-2.5 sm:px-5">
353 <span className="font-medium text-fg">All in</span>
354 <span className="block text-xs text-faint">
355 {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"}
356 </span>
357 </td>
358 <td className="tabular px-4 py-2.5 text-right font-medium">{usd(moneyIn, { cents: true })}</td>
359 <td className="tabular px-4 py-2.5 text-right font-medium">{usd(spent, { cents: true })}</td>
360 <td className="tabular px-4 py-2.5 text-right font-medium text-fg sm:pr-5">{usd(net, { signed: true, cents: true })}</td>
361 </tr>
362 </tbody>
363 </table>
364 </div>
365 <p className="mt-2 text-xs text-faint">
366 Credits from g1t, {report.since} to {report.until}: {usd(o.creditsGivenMicros ?? 0, { cents: true })} given, {usd(o.creditsUsedMicros ?? 0, { cents: true })}{" "}
367 spent on usage. What promotional and goodwill credit paid for is given away above, never money in; refunds took{" "}
368 {usd(o.creditsRefundedMicros ?? 0, { cents: true })} off money in on the days they refund.{" "}
369 <Link to="/credits" className="underline underline-offset-2">
370 Every credit
371 </Link>
372 </p>
373 <dl className="mt-3 grid grid-cols-2 gap-3 rounded-lg border border-line bg-surface px-4 py-3 text-sm sm:px-5">
374 <div>
375 <dt className="text-xs text-muted">Tax collected</dt>
376 <dd className="tabular text-fg">{usd(o.taxCollectedMicros ?? 0, { cents: true })}</dd>
377 <dd className="text-xs text-faint">Owed to tax authorities; never in money in or margin. Filed from Stripe Tax.</dd>
378 </div>
379 <div>
380 <dt className="text-xs text-muted">Card fees passed on</dt>
381 <dd className="tabular text-fg">{usd(o.cardFeesMicros ?? 0, { cents: true })}</dd>
382 <dd className="text-xs text-faint">Only payments from before the fee was folded into prices; not revenue either.</dd>
383 </div>
384 </dl>
385 </>
386 );
387}
388
389function SpendSection({ caps, range, error }: { caps: CostsReport["caps"]; range: number; error: string | null }) {
390 const { rows, totalMicros } = spendRows(caps);
391 const net = caps.revenueMicros - totalMicros;
392 return (
393 <Section
394 className="mt-6"
395 id="spend"
396 title="g1t's own spend"
397 description="What g1t pays for itself this calendar month, today included: accounts on a 100% discount, the trial and open-source pools, free workspaces' overruns, and usage charged without real money behind it. Each is what the work cost g1t, never its price, counted as each charge settled: the price book's cost of sandboxes and builds. Two caps hold it: each 100%-discount account's monthly budget, and a daily breaker on all of it that pauses new compute g1t would pay for: workflows, checks and the merge queue."
398 >
399 <div className="grid gap-3 lg:grid-cols-2">
400 <CapMeter
401 label={`Today, ${caps.day} (UTC)`}
402 used={caps.todayMicros}
403 cap={caps.dailyCapMicros}
404 hint={
405 caps.tripped ? (
406 <span className="text-danger">
407 Breaker open{caps.trippedAt ? <> since <When at={caps.trippedAt} time /></> : null}: new work g1t pays for waits until 00:00 UTC.
408 </span>
409 ) : caps.liftedBy ? (
410 <>
411 Lifted for today by {caps.liftedBy}
412 {caps.liftNote ? `: “${caps.liftNote}”` : ""}.
413 </>
414 ) : (
415 "Paying workspaces on a live card are never paused. PLATFORM_DAILY_SPEND_CAP_MICROS."
416 )
417 }
418 />
419 {caps.comped.map((b) => (
420 <CapMeter
421 key={b.account}
422 label={`${b.name}, ${caps.month} (100% discount)`}
423 used={b.usedMicros}
424 cap={b.ceilingMicros}
425 hint={
426 <>
427 {b.ceilingMicros > 0 && b.usedMicros >= b.ceilingMicros ? (
428 <span className="text-danger">Used up: new work on it is refused. </span>
429 ) : null}
430 {b.defaultCeiling ? "The default budget (COMPED_MONTHLY_CEILING_MICROS)" : "Its own budget, in its terms"}. Raise it on{" "}
431 <Link to={`/workspaces/${encodeURIComponent(b.name)}#billing`} className="underline underline-offset-2">
432 its account
433 </Link>
434 : Terms, Limit.
435 </>
436 }
437 />
438 ))}
439 </div>
440 {(caps.tripped || error) && (
441 <form method="post" action="#spend" className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-end">
442 <input type="hidden" name="intent" value="lift" />
443 <Field label="Why lift it" hint="Recorded in the audit log.">
444 <Input name="note" required minLength={5} maxLength={500} placeholder="e.g. Launch day; watching it" className="sm:min-w-[24rem]" />
445 </Field>
446 <Button type="submit" variant="danger">
447 Lift for today
448 </Button>
449 </form>
450 )}
451 {error && (
452 <div className="mt-3">
453 <Notice tone="error">{error}</Notice>
454 </div>
455 )}
456 <div className="-mx-4 mt-5 overflow-x-auto sm:-mx-5">
457 <table className="w-full min-w-136 text-sm">
458 <thead>
459 <tr className="border-b border-line text-left text-xs text-muted">
460 <th className="px-4 py-2 font-medium sm:px-5">{caps.month}, so far</th>
461 <th className="px-4 py-2 text-right font-medium sm:pr-5">g1t paid</th>
462 </tr>
463 </thead>
464 <tbody>
465 {rows.map((row) => (
466 <tr key={row.key} className="border-b border-line align-top">
467 <td className="px-4 py-2.5 sm:px-5">
468 {row.title}
469 <span className="block text-xs text-faint">{row.note}</span>
470 </td>
471 <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(row.micros)}</td>
472 </tr>
473 ))}
474 <tr className="border-b border-line font-medium">
475 <td className="px-4 py-2.5 sm:px-5">All of it</td>
476 <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(totalMicros)}</td>
477 </tr>
478 <tr className="border-b border-line">
479 <td className="px-4 py-2.5 sm:px-5">
480 Money in
481 <span className="block text-xs text-faint">Usage paid for with real money and the plan, as last reconciled</span>
482 </td>
483 <td className="tabular px-4 py-2.5 text-right sm:pr-5">{usd(caps.revenueMicros)}</td>
484 </tr>
485 <tr>
486 <td className="px-4 py-2.5 sm:px-5">Money in less what g1t paid</td>
487 <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>
488 </tr>
489 </tbody>
490 </table>
491 </div>
492 <p className="mt-3 text-xs text-muted">
493 Why this differs from the statement above: this is {caps.month} so far, the statement the last {range} days. The statement takes Cloudflare&apos;s bill as the
494 cost of what Cloudflare runs, so sandboxes and builds inside Cloudflare&apos;s included usage cost nothing there, and here what the price book says they
495 cost. Cloudflare&apos;s subscriptions are counted the same way in both: each day its billing cycle&apos;s share of the month&apos;s price.
496 {(caps.resetMicros ?? 0) > 0 && (
497 <>
498 {" "}
499 And {usd(caps.resetMicros ?? 0, { cents: true })} of this was spent on {(caps.resetWorkspaces ?? []).join(", ")} before a testing reset wiped
500 {(caps.resetWorkspaces ?? []).length === 1 ? " its" : " their"} billing: still g1t&apos;s spend, but the statement has it only where the reset kept
501 it, as given away.
502 </>
503 )}
504 </p>
505 </Section>
506 );
507}
508
509
510/**
511 * The platform pause and the hourly usage watch (billing's platform.rs;
512 * docs.g1t.sh/guides/deploy-to-cloudflare/#spend-guardrails): four levels
513 * staff can pause across g1t, what Cloudflare counted in the last hour
514 * against each threshold, and the last day's breaches.
515 */
516function PlatformSection({
517 guard,
518 unavailable,
519 failed,
520}: {
521 guard: PlatformGuard | null;
522 unavailable: string | null;
523 failed: CostsActionResult | null;
524}) {
525 return (
526 <Section
527 className="mt-6"
528 id="platform"
529 title="Platform pause"
530 description="What Cloudflare counted for all of g1t, read at a quarter past each hour: Workers, D1, Queues, Durable Objects, KV and Artifacts, each against an hourly threshold (PLATFORM_HOURLY_* in billing's wrangler.jsonc). A breach emails staff once per metric every 6 hours. Five times a threshold pauses what that metric feeds, of the levels AUTO_PAUSE names. Any level can be paused here by hand; every service sees a change within 30 seconds."
531 >
532 {!guard ? (
533 <Notice tone="warn">Billing did not answer for the platform pause: {unavailable}</Notice>
534 ) : (
535 <>
536 {!guard.can_read && (
537 <div className="mb-4">
538 <Notice tone="warn">Billing has no Cloudflare token with Account Analytics Read, so the hourly watch reads nothing. The pause still works.</Notice>
539 </div>
540 )}
541 {(guard.blind ?? []).length > 0 && (
542 <div className="mb-4">
543 <Notice tone="warn">
544 The watcher can&apos;t see: {(guard.blind ?? []).map((b) => b.key).join(", ")}
545 {guard.last_run ? ` (the run for ${guard.last_run})` : ""}. Their metrics are not watched until it is fixed; three runs in a row email
546 staff.
547 <ul className="mt-2 space-y-1 text-xs">
548 {(guard.blind ?? []).map((b) => (
549 <li key={b.key}>
550 <code>{b.dataset}</code> ({b.key}): {b.error}
551 </li>
552 ))}
553 </ul>
554 </Notice>
555 </div>
556 )}
557 {guard.empty && (
558 <div className="mb-4">
559 <Notice tone="warn">
560 Every dataset answered with no rows{guard.last_run ? ` in the run for ${guard.last_run}` : ""}, the month so far included: likely the wrong
561 account or a token that cannot see its analytics. The watcher sees nothing.
562 </Notice>
563 </div>
564 )}
565 {failed?.section === "platform" && (
566 <div className="mb-4">
567 <Notice tone="error">{failed.error}</Notice>
568 </div>
569 )}
570 <div className="grid gap-3 lg:grid-cols-2">
571 {PAUSE_LEVELS.map(({ level, title, stops }) => {
572 const state = guard.levels.find((l) => l.level === level);
573 const paused = state?.paused ?? false;
574 const error = failed?.section === `pause-${level}` ? failed.error : null;
575 return (
576 <div key={level} className={`rounded-lg border p-4 ${paused ? "border-danger/50 bg-danger/8" : "border-line"}`}>
577 <div className="flex items-center justify-between gap-2">
578 <span className="font-medium">{title}</span>
579 {paused ? <Badge tone="danger">Paused</Badge> : <Badge tone="mint">Running</Badge>}
580 </div>
581 <p className="mt-1 text-xs text-muted">{stops}</p>
582 {state?.set_at && (
583 <p className="mt-2 text-xs text-faint">
584 {paused ? "Paused" : "Resumed"} <When at={state.set_at} time /> by {state.auto ? "the usage watcher" : state.set_by}
585 {state.note ? `: “${state.note}”` : ""}
586 </p>
587 )}
588 <form method="post" action="#platform" className="mt-3 flex flex-col gap-2 sm:flex-row sm:items-end">
589 <input type="hidden" name="intent" value={paused ? "resume" : "pause"} />
590 <input type="hidden" name="level" value={level} />
591 <Field label={paused ? "Why resume it" : "Why pause it"} hint="Recorded in the audit log.">
592 <Input name="note" required minLength={5} maxLength={500} placeholder={paused ? "e.g. Fixed the loop in search" : "e.g. KV lists runaway"} />
593 </Field>
594 <Button type="submit" variant={paused ? "primary" : "danger"}>
595 {paused ? "Resume" : "Pause"}
596 </Button>
597 </form>
598 {error && (
599 <div className="mt-2">
600 <Notice tone="error">{error}</Notice>
601 </div>
602 )}
603 </div>
604 );
605 })}
606 </div>
607 <p className="mt-3 text-xs text-muted">
608 A severe breach may pause: {guard.auto_pause.length ? guard.auto_pause.join(", ") : "nothing (AUTO_PAUSE is empty)"}.
609 </p>
610
611 <h3 className="mt-6 text-sm font-medium">Breaches in the last 24 hours</h3>
612 {guard.breaches.length === 0 ? (
613 <p className="mt-2 text-sm text-muted">None.</p>
614 ) : (
615 <ul className="mt-2 space-y-2 text-sm">
616 {guard.breaches.map((b) => (
617 <li key={b.id} className="rounded-md border border-line px-3 py-2">
618 <div className="flex flex-wrap items-center gap-2">
619 <Badge tone={b.severe ? "danger" : "warn"}>{b.severe ? "Severe" : b.rule === "spike" ? "Spike" : "Over threshold"}</Badge>
620 <span className="text-xs text-faint">
621 <When at={b.opened_at} time />
622 {b.emailed_at ? ", emailed" : ""}
623 </span>
624 </div>
625 <p className="mt-1 text-fg-soft">{b.detail}</p>
626 </li>
627 ))}
628 </ul>
629 )}
630
631 <UsageTable title={guard.hour ? `The hour from ${guard.hour}` : "The last hour"} metrics={guard.last_hour} hourly />
632 <UsageTable title={`${guard.month}, so far`} metrics={guard.month_to_date} hourly={false} />
633 <p className="mt-3 text-xs text-muted">
634 Ids are Cloudflare&apos;s. <code>node scripts/ops/platform-usage.mjs</code> names them and shows the last 24 hours per script, queue, database and
635 namespace.
636 </p>
637 </>
638 )}
639 </Section>
640 );
641}
642
643function UsageTable({ title, metrics, hourly }: { title: string; metrics: PlatformMetric[]; hourly: boolean }) {
644 return (
645 <>
646 <h3 className="mt-6 text-sm font-medium">{title}</h3>
647 {metrics.length === 0 ? (
648 <p className="mt-2 text-sm text-muted">Nothing read yet.</p>
649 ) : (
650 <div className="-mx-4 mt-2 overflow-x-auto sm:-mx-5">
651 <table className="w-full min-w-xl text-sm">
652 <thead>
653 <tr className="border-b border-line text-left text-xs text-muted">
654 <th className="px-4 py-2 font-medium sm:px-5">Metric</th>
655 <th className="px-4 py-2 text-right font-medium">Count</th>
656 {hourly && <th className="px-4 py-2 text-right font-medium">Of threshold</th>}
657 <th className="px-4 py-2 font-medium sm:pr-5">Most from</th>
658 </tr>
659 </thead>
660 <tbody>
661 {metrics.map((m) => {
662 const share = thresholdShare(m.value, m.threshold);
663 return (
664 <tr key={m.metric} className="border-b border-line last:border-0">
665 <td className="px-4 py-2.5 sm:px-5">{m.title}</td>
666 <td className="tabular px-4 py-2.5 text-right">{count(m.value)}</td>
667 {hourly && (
668 <td className={`tabular px-4 py-2.5 text-right ${share != null && share > 100 ? "text-danger" : "text-muted"}`}>
669 {share == null ? "—" : `${share}% of ${count(m.threshold)}`}
670 </td>
671 )}
672 <td className="max-w-[16rem] truncate px-4 py-2.5 text-xs text-faint sm:pr-5">
673 {m.top_name ? `${m.top_name} (${count(m.top_value ?? 0)})` : "—"}
674 </td>
675 </tr>
676 );
677 })}
678 </tbody>
679 </table>
680 </div>
681 )}
682 </>
683 );
684}