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.

110 linesCodeBlameRaw
1/**
2 * Costs & margin and Bill & pricing: one report from billing, read by both
3 * pages, and the changes staff make on either.
4 */
5import { data, redirect } from "react-router";
6
7import { parseCostSettings, parseMapping, parsePauseLevel, parseRange } from "./costs";
8import { admin } from "./services.server";
9import { settle } from "./settle";
10import { requireStaff } from "./staff";
11
12export const DONE: Record<string, string> = {
13 run: "Ran the analysis: read Cloudflare's bill, reconciled the last 31 days and checked the alerts. The figures below are fresh.",
14 approved: "Approved. A fall applies now; a rise after the notice period, and owners on the plan are emailed.",
15 rejected: "Rejected, with the note kept for whoever measures it next.",
16 settings: "Guardrails saved. They apply from the next run.",
17 mapping: "Mapping saved. It applies from the next run; read the bill now to see it.",
18 removed: "Mapping removed.",
19 lifted: "Breaker lifted for the rest of today (UTC). Paused work starts again; it is recorded in the audit log.",
20 paused: "Paused across g1t. Every service sees it within 30 seconds; it is recorded in the audit log.",
21 resumed: "Resumed across g1t. Every service sees it within 30 seconds; it is recorded in the audit log.",
22};
23
24export type CostsActionResult = { error: string; section: string; values?: Record<string, string> };
25
26export async function costsLoader(request: Request, context: unknown) {
27 requireStaff(context as Parameters<typeof requireStaff>[0]);
28 const url = new URL(request.url);
29 const range = parseRange(url.searchParams.get("days"));
30 const [report, guard] = await Promise.all([settle(admin.costs(range)), settle(admin.platformGuard())]);
31 const done = url.searchParams.get("done");
32 return {
33 range,
34 bucket: url.searchParams.get("product"),
35 report: report.ok ? report.value : null,
36 error: report.ok ? null : report.error,
37 // The platform pause and usage watch (billing's platform.rs).
38 guard: guard.ok ? guard.value : null,
39 guardError: guard.ok ? null : guard.error,
40 done: done ? (DONE[done] ?? null) : null,
41 };
42}
43
44export async function costsAction(request: Request, context: unknown) {
45 const staff = requireStaff(context as Parameters<typeof requireStaff>[0]);
46 const form = await request.formData();
47 const intent = String(form.get("intent") ?? "");
48 const fail = (section: string, error: string) =>
49 data<CostsActionResult>({ error, section, values: Object.fromEntries([...form.entries()].map(([k, v]) => [k, String(v)])) }, { status: 422 });
50 const back = (done: string, hash = "") => {
51 const url = new URL(request.url);
52 url.searchParams.set("done", done);
53 return redirect(`${url.pathname}${url.search}${hash}`);
54 };
55 if (intent === "run") {
56 const run = await settle(admin.runCosts(staff.email));
57 if (!run.ok) return fail("run", `Billing did not answer: ${run.error}`);
58 if (!run.value.ok) return fail("run", run.value.error.message);
59 if (run.value.value.problems.length > 0) return fail("run", run.value.value.problems.join(" "));
60 throw back("run");
61 }
62 if (intent === "lift") {
63 const note = String(form.get("note") ?? "")
64 .trim()
65 .slice(0, 500);
66 if (note.length < 5) return fail("lift", "Say why it is lifted, for whoever looks next.");
67 const result = await settle(admin.liftBreaker(note, staff.email));
68 if (!result.ok) return fail("lift", `Billing did not answer: ${result.error}`);
69 if (!result.value.ok) return fail("lift", result.value.error.message);
70 throw back("lifted", "#spend");
71 }
72 if (intent === "pause" || intent === "resume") {
73 const level = parsePauseLevel(form.get("level"));
74 if (!level) return fail("platform", "Pick compute, schedules, indexing or renders.");
75 const note = String(form.get("note") ?? "")
76 .trim()
77 .slice(0, 500);
78 if (note.length < 5) return fail(`pause-${level}`, "Say why, for whoever looks next.");
79 const result = await settle(admin.setPause(level, intent === "pause", note, staff.email));
80 if (!result.ok) return fail(`pause-${level}`, `Billing did not answer: ${result.error}`);
81 if (!result.value.ok) return fail(`pause-${level}`, result.value.error.message);
82 throw back(intent === "pause" ? "paused" : "resumed", "#platform");
83 }
84 if (intent === "decide") {
85 const id = String(form.get("id") ?? "");
86 const decision = form.get("decision") === "approve" ? "approve" : "reject";
87 const note = String(form.get("note") ?? "")
88 .trim()
89 .slice(0, 500);
90 if (decision === "reject" && note.length < 5) return fail(`proposal-${id}`, "Say why it is rejected, for whoever measures it next.");
91 const result = await admin.decideProposal(id, decision, note, staff.email);
92 if (!result.ok) return fail(`proposal-${id}`, result.error.message);
93 throw back(decision === "approve" ? "approved" : "rejected", "#proposals");
94 }
95 if (intent === "settings") {
96 const parsed = parseCostSettings(form);
97 if (!parsed.ok) return fail("settings", parsed.error);
98 const result = await admin.setCostSettings(parsed.value, staff.email);
99 if (!result.ok) return fail("settings", result.error.message);
100 throw back("settings", "#guardrails");
101 }
102 if (intent === "mapping") {
103 const parsed = parseMapping(form);
104 if (!parsed.ok) return fail("mapping", parsed.error);
105 const result = await admin.setCostMapping(parsed.value, staff.email);
106 if (!result.ok) return fail("mapping", result.error.message);
107 throw back(parsed.value.remove ? "removed" : "mapping", "#mappings");
108 }
109 return fail("run", "Unknown change.");
110}