Commit

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)

syntaqxcommitted Parent3e0c0c1Browse files
5 files+99−190/5 viewed
+40−0
6565 font-variant-numeric: tabular-nums;
6666 }
6767 }
68+
69+/*
70+ * sudo ships no JavaScript, so a long-running button says it is running
71+ * with CSS alone: a clicked button keeps focus while the old page waits for
72+ * the response. `:focus-visible` is left out, so tabbing to it does not
73+ * read as running.
74+ */
75+.run-button .run-busy,
76+.run-button + .run-note {
77+ display: none;
78+}
79+.run-button:focus:not(:focus-visible) .run-idle {
80+ display: none;
81+}
82+.run-button:focus:not(:focus-visible) .run-busy {
83+ display: inline-flex;
84+ align-items: center;
85+ gap: 0.5rem;
86+}
87+.run-button:focus:not(:focus-visible) + .run-note {
88+ display: inline;
89+}
90+.run-spinner {
91+ width: 0.8rem;
92+ height: 0.8rem;
93+ border-radius: 9999px;
94+ border: 2px solid currentColor;
95+ border-right-color: transparent;
96+ animation: run-spin 0.8s linear infinite;
97+}
98+@keyframes run-spin {
99+ to {
100+ transform: rotate(360deg);
101+ }
102+}
103+@media (prefers-reduced-motion: reduce) {
104+ .run-spinner {
105+ animation: none;
106+ }
107+}
+30−17
11 import type { ReactNode } from "react";
2−import { Form, Link, data, redirect, useNavigation } from "react-router";
2+import { Link, data, redirect } from "react-router";
33
44 import type { CostsReport, PriceProposal } from "@g1t/contracts";
55
613613 }
614614
615615 /** g1t's own spend: the daily breaker, comped budgets, and this month by what paid. */
616−/** The button that runs the nightly analysis now, saying so while it runs. */
616+/**
617+ * The button that runs the nightly analysis now. sudo runs no JavaScript,
618+ * so app.css's run-button rules say it is running while the page waits.
619+ */
617620 function RunButton() {
618− const navigation = useNavigation();
619− const running = navigation.state !== "idle" && navigation.formData?.get("intent") === "run";
620621 return (
621− <Form method="post" className="flex items-center gap-3">
622+ <form method="post" className="flex items-center gap-3">
622623 <input type="hidden" name="intent" value="run" />
623− <Button variant="quiet" type="submit" disabled={running}>
624− {running ? "Running the analysis…" : "Run the analysis now"}
624+ <Button variant="quiet" type="submit" className="run-button">
625+ <span className="run-idle">Run the analysis now</span>
626+ <span className="run-busy">
627+ <span className="run-spinner" aria-hidden="true" />
628+ Running the analysis…
629+ </span>
625630 </Button>
626− {running && <span className="text-xs text-muted">Reading the bill and reconciling 31 days; about a minute.</span>}
627− </Form>
631+ <span className="run-note text-xs text-muted">Reading the bill and reconciling 31 days; about a minute.</span>
632+ </form>
628633 );
629634 }
630635
638643 // Reports from before the statement have only the totals.
639644 const given = o.givenMicros ?? 0;
640645 const usageCost = o.usageCostMicros ?? o.costMicros - given;
641− const usageMargin = o.usageMarginMicros ?? o.usageMicros - usageCost;
646+ const included = o.includedMicros ?? 0;
647+ const usageIn = o.usageMicros + included;
648+ const usageMargin = o.usageMarginMicros ?? usageIn - usageCost;
642649 const usagePercent = o.usageMarginPercent !== undefined ? o.usageMarginPercent : null;
643650 const running = o.runningCostMicros ?? 0;
644651 const unmapped = o.unmappedCostMicros ?? 0;
656663 const rows: { title: string; note: string; in: number | null; cost: number; result: number | null; tone?: "danger" | "warn" | "muted" }[] = [
657664 {
658665 title: "Usage sold",
659− note: "What workspaces paid for usage, against what that usage cost",
660− in: o.usageMicros,
666+ note:
667+ included > 0
668+ ? `What workspaces paid for usage (${usd(o.usageMicros)}) and their plan's included usage (${usd(included)}), against what it cost`
669+ : "What workspaces paid for usage, against what that usage cost",
670+ in: usageIn,
661671 cost: usageCost,
662672 result: usageMargin,
663673 tone: usageMargin < 0 ? "danger" : undefined,
664674 },
665675 {
666676 title: "Running g1t",
667− note: "Plans, against Workers, D1, KV, Queues and the rest of the platform",
668− in: o.plansMicros,
677+ note:
678+ included > 0
679+ ? `Plans (${usd(o.plansMicros)}) less the included usage they paid for, against Workers, D1, KV, Queues and the rest`
680+ : "Plans, against Workers, D1, KV, Queues and the rest of the platform",
681+ in: o.plansMicros - included,
669682 cost: running,
670− result: o.plansMicros - running,
671− tone: o.plansMicros - running < 0 ? "warn" : undefined,
683+ result: o.plansMicros - included - running,
684+ tone: o.plansMicros - included - running < 0 ? "warn" : undefined,
672685 },
673686 {
674687 title: "Cloudflare subscriptions",
697710 <Stat
698711 label="Margin on usage sold"
699712 value={percentLabel(usagePercent)}
700− hint={`${usd(o.usageMicros)} paid for usage that cost ${usd(usageCost)}`}
713+ hint={`${usd(usageIn)} paid for usage that cost ${usd(usageCost)}`}
701714 tone={marginTone(usagePercent, floor)}
702715 />
703716 <Stat
+5−0
24322432 /// `cost_micros` by who g1t pays: Cloudflare's bill (billed amounts,
24332433 /// after the included allowances), and model providers (the ledger's
24342434 /// cost of the tokens, which Cloudflare's bill does not show).
2435+ /// What the plan's included usage paid for, at price (the ledger's
2436+ /// `credit_micros`, comped workspaces left out): money in for usage,
2437+ /// paid out of `plans_micros`.
2438+ #[serde(default)]
2439+ pub included_micros: i64,
24352440 #[serde(default)]
24362441 pub cloudflare_cost_micros: i64,
24372442 #[serde(default)]
+2−0
10811081 givenTrialMicros?: number;
10821082 givenPoolMicros?: number;
10831083 /** costMicros by who g1t pays: Cloudflare's bill (billed, after included allowances) and model providers (tokens, not on Cloudflare's bill). */
1084+ /** What the plan's included usage paid for, at price: money in for usage, paid out of plansMicros. */
1085+ includedMicros?: number;
10841086 cloudflareCostMicros?: number;
10851087 modelsCostMicros?: number;
10861088 };
+22−2
14471447 let sold = (overall.cost_micros - overall.given_micros).max(0);
14481448 overall.sold_margin_micros = revenue - sold;
14491449 overall.sold_margin_percent = margin_percent(revenue, sold);
1450− overall.usage_margin_micros = overall.usage_micros - overall.usage_cost_micros;
1451− overall.usage_margin_percent = margin_percent(overall.usage_micros, overall.usage_cost_micros);
1450+ // The plan's included usage was paid for by the plan's price: it is
1451+ // money in for the usage it covered, and out of what the plans
1452+ // leave for running g1t.
1453+ #[derive(Deserialize)]
1454+ struct Included {
1455+ micros: Option<i64>,
1456+ }
1457+ overall.included_micros = self
1458+ .db
1459+ .prepare(format!(
1460+ "SELECT SUM(COALESCE(credit_micros, 0)) AS micros FROM ledger
1461+ WHERE kind = 'usage' AND created_at >= ?1 AND created_at <= ?2 AND workspace NOT IN ({})",
1462+ crate::sales::INTERNAL_SQL
1463+ ))
1464+ .bind(&[since.as_str().into(), format!("{until}T23:59:59.999Z").into()])?
1465+ .first::<Included>(None)
1466+ .await?
1467+ .and_then(|r| r.micros)
1468+ .unwrap_or(0);
1469+ let usage_in = overall.usage_micros + overall.included_micros;
1470+ overall.usage_margin_micros = usage_in - overall.usage_cost_micros;
1471+ overall.usage_margin_percent = margin_percent(usage_in, overall.usage_cost_micros);
14521472 let mut products: Vec<ProductMargin> = products.into_values().collect();
14531473 products.sort_by_key(|p| std::cmp::Reverse(p.cost_micros.max(p.value_micros)));
14541474