Skip to content
221 linesCodeBlameRaw
1/**
2 * The parts of g1t the page lists, and how each is checked. Every address
3 * comes from the Worker's vars, so an installation of its own names its own
4 * hosts; a part whose address is empty is not part of that installation
5 * and is left off the page.
6 *
7 * Checks go over the public internet, as people reach g1t, so they measure
8 * what people see. Billing has no public address of its own, so it is the
9 * one part checked through a service binding, when there is one.
10 */
11
12/** The Worker's vars that say where things are. */
13export type Targets = {
14 /** The site: `https://g1t.sh`. */
15 SITE_URL: string;
16 /** The API: `https://api.g1t.sh`. */
17 API_URL: string;
18 MCP_URL: string;
19 DOCS_URL: string;
20 /** Where deployed apps live: `https://g1t.page`. */
21 PAGES_URL: string;
22 MODELS_URL: string;
23 /** A public repository that always exists, `owner/name`, to fetch refs of. */
24 PROBE_REPO: string;
25 /** The site as browsers reach it, when the checks reach it another way. */
26 PUBLIC_SITE_URL?: string;
27};
28
29/** One request a check makes, and the answer that means the part works. */
30export type Step = {
31 url: string;
32 headers?: Record<string, string>;
33 /** The status that means it works; any 2xx when absent. */
34 expect?: number;
35 /**
36 * A page people open in a browser: asked for with a browser's user agent
37 * (probe.ts `BROWSER_USER_AGENT`), so the site streams it as it does for
38 * people, and the time is to the first byte, not to a crawler's full render.
39 */
40 browser?: boolean;
41};
42
43export type Check =
44 | { kind: "http"; steps: Step[] }
45 /** Billing, through its binding: reading its price book. */
46 | { kind: "billing" }
47 /** Git storage, through the repos service binding: how the store answered lately. */
48 | { kind: "storage" }
49 /** No check yet: the page says so rather than showing green. */
50 | { kind: "none" };
51
52export type ComponentInfo = {
53 key: string;
54 name: string;
55 address: string;
56 checks: string;
57 /** Whether g1t as a whole is down when this is. */
58 core: boolean;
59 check: Check;
60 /** Slower than this, it counts as degraded; `SLOW_MS` when absent. */
61 slowMs?: number;
62};
63
64/**
65 * The page-speed budget the status page holds the site to: the slower of a
66 * public project page and Explore, to the first byte of the answer, from a
67 * Cloudflare data centre. docs/PERFORMANCE.md has the targets.
68 */
69export const SPEED_BUDGET_MS = 800;
70
71/** A token no one holds: the API asks identity about it and refuses it. */
72export const NO_TOKEN = `g1t_${"0".repeat(40)}`;
73
74function host(url: string): string {
75 try {
76 return new URL(url).host;
77 } catch {
78 return url;
79 }
80}
81
82const trim = (url: string | undefined) => (url ?? "").trim().replace(/\/+$/, "");
83
84/** Git storage counts as degraded when its calls took longer than this on average. */
85export const STORAGE_SLOW_MS = 1500;
86
87/** The parts, in the order the page lists them. `storage`: whether the repos service is bound. */
88export function components(vars: Partial<Targets>, billing = true, storage = false): ComponentInfo[] {
89 const site = trim(vars.SITE_URL);
90 const api = trim(vars.API_URL);
91 const mcp = trim(vars.MCP_URL);
92 const docs = trim(vars.DOCS_URL);
93 const pages = trim(vars.PAGES_URL);
94 const models = trim(vars.MODELS_URL);
95 const repo = (vars.PROBE_REPO ?? "").trim();
96 // What the page shows as the site's address: where people reach it.
97 const shown = host(trim(vars.PUBLIC_SITE_URL) || site);
98 const list: (ComponentInfo | null)[] = [
99 site
100 ? {
101 key: "site",
102 name: "Website and sign-in",
103 address: shown,
104 checks: api
105 ? "The sign-in page loading, and the account service answering an access token through the API."
106 : "The sign-in page loading.",
107 core: true,
108 check: {
109 kind: "http",
110 steps: [
111 { url: `${site}/login`, browser: true },
112 ...(api ? [{ url: `${api}/v1/user`, headers: { authorization: `Bearer ${NO_TOKEN}` }, expect: 401 }] : []),
113 ],
114 },
115 }
116 : null,
117 api
118 ? {
119 key: "api",
120 name: "API",
121 address: host(api),
122 checks: "The API's front page.",
123 core: true,
124 check: { kind: "http", steps: [{ url: `${api}/` }] },
125 }
126 : null,
127 site && repo
128 ? {
129 key: "git",
130 name: "Git and repositories",
131 address: `${shown}/<owner>/<repo>.git`,
132 checks: "Listing the branches of a public repository over HTTPS, the first step of every clone.",
133 core: true,
134 check: { kind: "http", steps: [{ url: `${site}/${repo}.git/info/refs?service=git-upload-pack` }] },
135 }
136 : null,
137 storage && site
138 ? {
139 key: "storage",
140 name: "Git storage",
141 address: "Where every repository is kept",
142 checks: `How g1t's git storage answered over the last five minutes, as g1t saw it: failures, rate limits, and how long calls took. Rate limited, or slower than ${STORAGE_SLOW_MS} ms on average, counts as degraded.`,
143 core: false,
144 check: { kind: "storage" },
145 slowMs: STORAGE_SLOW_MS,
146 }
147 : null,
148 site && repo
149 ? {
150 key: "speed",
151 name: "Page speed",
152 address: `${shown}/${repo}`,
153 checks: `A public project page and Explore each answering within ${SPEED_BUDGET_MS} ms. Slower counts as degraded.`,
154 core: false,
155 check: { kind: "http", steps: [{ url: `${site}/${repo}`, browser: true }, { url: `${site}/explore`, browser: true }] },
156 slowMs: SPEED_BUDGET_MS,
157 }
158 : null,
159 mcp
160 ? {
161 key: "mcp",
162 name: "MCP server",
163 address: host(mcp),
164 checks: "The MCP server's description.",
165 core: false,
166 check: { kind: "http", steps: [{ url: `${mcp}/`, headers: { accept: "application/json" } }] },
167 }
168 : null,
169 docs
170 ? {
171 key: "docs",
172 name: "Documentation",
173 address: host(docs),
174 checks: "The documentation's front page.",
175 core: false,
176 check: { kind: "http", steps: [{ url: `${docs}/` }] },
177 }
178 : null,
179 pages
180 ? {
181 key: "deployments",
182 name: "Deployments",
183 address: `*.${host(pages)}`,
184 checks: `${host(pages)} answering. Each deployed app is not checked one by one.`,
185 core: false,
186 check: { kind: "http", steps: [{ url: `${pages}/` }] },
187 }
188 : null,
189 models
190 ? {
191 key: "agents",
192 name: "Agents' model proxy",
193 address: host(models),
194 checks: "The proxy agents reach their model through. The model providers behind it are not checked.",
195 core: false,
196 check: { kind: "http", steps: [{ url: `${models}/` }] },
197 }
198 : null,
199 models
200 ? {
201 key: "sandboxes",
202 name: "Sandboxes",
203 address: "Agents, checks, workflows and builds",
204 checks: "Starting a sandbox costs money and takes seconds, so it is not checked from here yet.",
205 core: false,
206 check: { kind: "none" },
207 }
208 : null,
209 billing && site
210 ? {
211 key: "billing",
212 name: "Billing",
213 address: `${shown}/<workspace>/-/billing`,
214 checks: "The billing service reading its price book. Stripe itself is not checked.",
215 core: false,
216 check: { kind: "billing" },
217 }
218 : null,
219 ];
220 return list.filter((c): c is ComponentInfo => c != null);
221}