need a status for my status for my ..
nate.tngl.io
1import { Resvg, initWasm } from "@resvg/resvg-wasm";
2import resvgWasm from "@resvg/resvg-wasm/index_bg.wasm";
3
4const ALLOWED_HOSTS = new Set([
5 "api.plyr.fm",
6 "moderation.plyr.fm",
7 "plyr-transcoder.fly.dev",
8 "plyr.fm",
9 "docs.plyr.fm",
10 "leaflet-search-backend.fly.dev",
11 "leaflet-search-tap.fly.dev",
12 "pub-search.waow.tech",
13 "relay.waow.tech",
14 "zlay.waow.tech",
15 "relay-eval.waow.tech",
16 "coral.fly.dev",
17 "coral-8hh.pages.dev",
18 "find-bufo.com",
19 "bufo-bot.fly.dev",
20 "pollz-backend.fly.dev",
21 "pollz.waow.tech",
22 "pds.zzstoatzz.io",
23 "typeahead.waow.tech",
24 "typeahead-ingester.fly.dev",
25 "prefect-server.waow.tech",
26 "prefect-metrics.waow.tech",
27 "zig-bsky-feed.fly.dev",
28 "at-me.wisp.place",
29 "status.zzstoatzz.io",
30 "pub-search-by-zzstoatzz.fastmcp.app",
31 "plyrfm.fastmcp.app",
32 "pdsx-by-zzstoatzz.fastmcp.app",
33 "relay-metrics.waow.tech",
34 "zlay-metrics.waow.tech",
35 "phi.zzstoatzz.io",
36]);
37
38const CORS_HEADERS = {
39 "Access-Control-Allow-Origin": "*",
40 "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
41 "Access-Control-Allow-Headers": "Content-Type",
42};
43
44interface Check {
45 url: string;
46 method?: string;
47 headers?: Record<string, string>;
48 body?: string;
49}
50
51function isAllowed(url: string): boolean {
52 try {
53 return ALLOWED_HOSTS.has(new URL(url).hostname);
54 } catch {
55 return false;
56 }
57}
58
59async function checkUrl(
60 check: Check
61): Promise<{ url: string; status: number; ok: boolean; ms: number }> {
62 const start = Date.now();
63 try {
64 const resp = await fetch(check.url, {
65 method: check.method || "GET",
66 headers: check.headers,
67 body: check.body,
68 redirect: "follow",
69 signal: AbortSignal.timeout(10_000),
70 });
71 return {
72 url: check.url,
73 status: resp.status,
74 ok: resp.ok,
75 ms: Date.now() - start,
76 };
77 } catch {
78 return { url: check.url, status: 0, ok: false, ms: Date.now() - start };
79 }
80}
81
82const TOTAL_SERVICES = 28;
83
84function buildOgSvg(): string {
85 const W = 1200;
86 const H = 630;
87 const cx = W / 2; // tree center x
88
89 let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">`;
90
91 // background + gradient
92 svg += `<defs>`;
93 svg += `<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">`;
94 svg += `<stop offset="0%" stop-color="#3fb950" stop-opacity="0.05"/>`;
95 svg += `<stop offset="100%" stop-color="#58a6ff" stop-opacity="0.05"/>`;
96 svg += `</linearGradient>`;
97 svg += `<radialGradient id="glow" cx="50%" cy="40%" r="50%">`;
98 svg += `<stop offset="0%" stop-color="#3fb950" stop-opacity="0.08"/>`;
99 svg += `<stop offset="100%" stop-color="#3fb950" stop-opacity="0"/>`;
100 svg += `</radialGradient>`;
101 svg += `</defs>`;
102 svg += `<rect width="${W}" height="${H}" fill="#0d1117"/>`;
103 svg += `<rect width="${W}" height="${H}" fill="url(#bg)"/>`;
104 svg += `<rect width="${W}" height="${H}" fill="url(#glow)"/>`;
105
106 // tree made of service dots — 24 dots arranged as an evergreen
107 // rows from top: 1, 2, 3, 4, 5, 6, 2 (trunk), 1 (trunk) = 24
108 const dotR = 7;
109 const spacingX = 28;
110 const spacingY = 32;
111 const treeTop = 80;
112
113 const treeRows = [1, 2, 3, 4, 5, 6, 2, 1];
114 // colors: canopy is green shades, trunk is muted
115 const rowColors = [
116 "#2ea043", // tip — darker green
117 "#3fb950",
118 "#3fb950",
119 "#3fb950",
120 "#56d364",
121 "#56d364", // wider — lighter green
122 "#8b6c3e", // trunk
123 "#8b6c3e", // trunk base
124 ];
125
126 let dotIndex = 0;
127 for (let row = 0; row < treeRows.length; row++) {
128 const count = treeRows[row];
129 const y = treeTop + row * spacingY;
130 const startX = cx - ((count - 1) * spacingX) / 2;
131
132 for (let j = 0; j < count; j++) {
133 const x = startX + j * spacingX;
134 const color = rowColors[row];
135 // slight glow on canopy dots
136 if (row < 6) {
137 svg += `<circle cx="${x}" cy="${y}" r="${dotR + 4}" fill="${color}" opacity="0.15"/>`;
138 }
139 svg += `<circle cx="${x}" cy="${y}" r="${dotR}" fill="${color}" opacity="0.9"/>`;
140 dotIndex++;
141 }
142 }
143
144 // title below tree
145 const textY = treeTop + treeRows.length * spacingY + 40;
146 svg += `<text x="${cx}" y="${textY}" text-anchor="middle" fill="#f0f6fc" font-family="JetBrains Mono" font-size="48" font-weight="bold">evergreen</text>`;
147 svg += `<text x="${cx}" y="${textY + 35}" text-anchor="middle" fill="#8b949e" font-family="JetBrains Mono" font-size="16">status page for services by @zzstoatzz.io · ${TOTAL_SERVICES} endpoints</text>`;
148
149 // footer
150 svg += `<text x="${cx}" y="${H - 30}" text-anchor="middle" fill="#30363d" font-family="JetBrains Mono" font-size="13">nate.tngl.io</text>`;
151
152 svg += `</svg>`;
153 return svg;
154}
155
156let wasmInitialized = false;
157let fontRegular: Uint8Array | null = null;
158let fontBold: Uint8Array | null = null;
159
160async function loadFonts(): Promise<Uint8Array[]> {
161 const urls = [
162 "https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf",
163 "https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Bold.ttf",
164 ];
165 if (!fontRegular || !fontBold) {
166 const [reg, bold] = await Promise.all(urls.map((u) => fetch(u)));
167 fontRegular = new Uint8Array(await reg.arrayBuffer());
168 fontBold = new Uint8Array(await bold.arrayBuffer());
169 }
170 return [fontRegular, fontBold];
171}
172
173async function handleOg(): Promise<Response> {
174 if (!wasmInitialized) {
175 await initWasm(resvgWasm);
176 wasmInitialized = true;
177 }
178
179 const fonts = await loadFonts();
180 const svg = buildOgSvg();
181 const resvg = new Resvg(svg, {
182 font: {
183 fontBuffers: fonts,
184 loadSystemFonts: false,
185 defaultFontFamily: "JetBrains Mono",
186 },
187 fitTo: { mode: "width", value: 1200 },
188 });
189 const png = resvg.render();
190 const pngData = png.asPng();
191
192 return new Response(pngData, {
193 headers: {
194 "Content-Type": "image/png",
195 "Cache-Control": "public, max-age=3600",
196 ...CORS_HEADERS,
197 },
198 });
199}
200
201export default {
202 async fetch(request: Request): Promise<Response> {
203 if (request.method === "OPTIONS") {
204 return new Response(null, { status: 204, headers: CORS_HEADERS });
205 }
206
207 const pathname = new URL(request.url).pathname;
208
209 // OG image endpoint
210 if (pathname === "/og") {
211 return handleOg();
212 }
213
214 // batch POST — accepts { checks: [{ url, method?, headers?, body? }] }
215 if (request.method === "POST") {
216 let body: { checks?: Check[] };
217 try {
218 body = await request.json();
219 } catch {
220 return Response.json(
221 { error: "invalid json" },
222 { status: 400, headers: CORS_HEADERS }
223 );
224 }
225
226 const checks = body.checks;
227 if (!Array.isArray(checks) || checks.length === 0) {
228 return Response.json(
229 { error: "checks must be a non-empty array" },
230 { status: 400, headers: CORS_HEADERS }
231 );
232 }
233
234 const blocked = checks.filter((c) => !isAllowed(c.url));
235 if (blocked.length > 0) {
236 return Response.json(
237 { error: "blocked hosts", blocked: blocked.map((c) => c.url) },
238 { status: 403, headers: CORS_HEADERS }
239 );
240 }
241
242 const results = await Promise.all(checks.map(checkUrl));
243 return Response.json(results, { headers: CORS_HEADERS });
244 }
245
246 // single GET
247 const url = new URL(request.url).searchParams.get("url");
248 if (!url) {
249 return Response.json(
250 { error: "missing ?url= parameter" },
251 { status: 400, headers: CORS_HEADERS }
252 );
253 }
254
255 if (!isAllowed(url)) {
256 return Response.json(
257 { error: "host not allowed" },
258 { status: 403, headers: CORS_HEADERS }
259 );
260 }
261
262 const result = await checkUrl({ url });
263 return Response.json(result, { headers: CORS_HEADERS });
264 },
265};