Openstatus www.openstatus.dev

chore: speed checker abort contoller (#1682)

* chore: abort controller

* feat: add new og status page

* fix: format

authored by

Maximilian Kaske and committed by
GitHub
6f46bdba c6ebe0d6

+145 -3
+1
apps/web/src/app/(landing)/play/checker/api/mock.ts
··· 35 35 { 36 36 type: "http", 37 37 status: 200, 38 + state: "success", 38 39 latency: 889, 39 40 headers: { 40 41 Age: "0",
+1
apps/web/src/app/(landing)/play/checker/api/route.ts
··· 25 25 // Create an array to store all the promises 26 26 const promises = AVAILABLE_REGIONS.map(async (region, index) => { 27 27 try { 28 + console.log(`Checking ${region}...`); 28 29 // Perform the fetch operation 29 30 const check = 30 31 process.env.NODE_ENV === "production"
+16 -2
apps/web/src/app/(landing)/play/checker/client.tsx
··· 123 123 124 124 startTransition(async () => { 125 125 async function fetchAndReadStream() { 126 + let toastId: string | number | undefined; 126 127 try { 127 - const toastId = toast.loading("Loading data from regions...", { 128 + toastId = toast.loading("Loading data from regions...", { 128 129 duration: Number.POSITIVE_INFINITY, 129 130 closeButton: false, 130 131 }); 131 132 133 + const abortController = new AbortController(); 134 + const timeoutId = setTimeout(() => abortController.abort(), 10_000); 135 + 132 136 const response = await fetch("/play/checker/api", { 133 137 method: "POST", 134 138 headers: { 135 139 "Content-Type": "application/json", 136 140 }, 137 141 body: JSON.stringify({ url, method }), 142 + signal: abortController.signal, 138 143 }); 144 + 145 + clearTimeout(timeoutId); 139 146 140 147 const reader = response?.body?.getReader(); 141 148 if (!reader) return; ··· 206 213 } 207 214 } catch (error) { 208 215 console.error("Error fetching data:", error); 209 - // Could add error handling/toast here 216 + if (error instanceof Error && error.name === "AbortError") { 217 + toast.error("Request timeout", { 218 + id: toastId, 219 + description: 220 + "The request took too long and was aborted after 7 seconds.", 221 + className: "text-destructive!", 222 + }); 223 + } 210 224 } 211 225 } 212 226
+126
apps/web/src/app/api/og/status/route.tsx
··· 1 + import { ImageResponse } from "next/og"; 2 + 3 + import { DESCRIPTION, TITLE } from "@/app/shared-metadata"; 4 + import { cn } from "@/lib/utils"; 5 + import { api } from "@/trpc/server"; 6 + import type { RouterOutputs } from "@openstatus/api"; 7 + import { format } from "date-fns"; 8 + import { SIZE } from "../utils"; 9 + 10 + export const runtime = "edge"; 11 + 12 + const FOOTER = "openstatus.dev"; 13 + 14 + type Page = NonNullable<RouterOutputs["statusPage"]["get"]>; 15 + 16 + function getContent(page?: Page | null) { 17 + switch (page?.status) { 18 + case "error": 19 + return { 20 + label: "Incident Ongoing", 21 + bg: "bg-rose-500/90 border-rose-500", 22 + text: "text-rose-500", 23 + }; 24 + case "degraded": 25 + return { 26 + label: "Degraded Performance", 27 + bg: "bg-orange-500/90 border-orange-500", 28 + text: "text-amber-500", 29 + }; 30 + case "info": 31 + return { 32 + label: "Maintenance", 33 + bg: "bg-blue-500/90 border-blue-500", 34 + text: "text-blue-500", 35 + }; 36 + case "success": 37 + return { 38 + label: "All Systems Operational", 39 + bg: "bg-green-500/90 border-green-500", 40 + text: "text-green-500", 41 + }; 42 + default: 43 + return { 44 + label: "Unknown", 45 + bg: "bg-gray-500/90 border-gray-500", 46 + text: "text-gray-500", 47 + }; 48 + } 49 + } 50 + 51 + export async function GET(req: Request) { 52 + const fontMonoRegular = await fetch( 53 + new URL("../../../../public/fonts/RobotoMono-Regular.ttf", import.meta.url), 54 + ).then((res) => res.arrayBuffer()); 55 + const fontMonoMedium = await fetch( 56 + new URL("../../../../public/fonts/RobotoMono-Medium.ttf", import.meta.url), 57 + ).then((res) => res.arrayBuffer()); 58 + const fontMonoBold = await fetch( 59 + new URL("../../../../public/fonts/RobotoMono-Bold.ttf", import.meta.url), 60 + ).then((res) => res.arrayBuffer()); 61 + 62 + const { searchParams } = new URL(req.url); 63 + 64 + const slug = searchParams.has("slug") ? searchParams.get("slug") : undefined; 65 + 66 + const page = await api.statusPage.get.query({ slug: slug || "" }); 67 + const content = getContent(page); 68 + 69 + const title = page ? page.title : TITLE; 70 + const description = page ? page.description : DESCRIPTION; 71 + const category = content?.label || "unknown"; 72 + const footer = 73 + page?.customDomain || page ? `${page?.slug}.openstatus.dev` : FOOTER; 74 + 75 + return new ImageResponse( 76 + <div tw="relative flex flex-col items-start justify-start w-full h-full bg-gray-100"> 77 + <div 78 + tw="flex flex-col h-full p-8 w-full" 79 + style={{ fontFamily: "Font Mono" }} 80 + > 81 + <div tw="flex flex-col justify-end flex-1 mb-8"> 82 + <div tw={cn("flex flex-row items-center text-2xl", content?.text)}> 83 + [<div tw={cn("rounded-full h-5 w-5 mr-2", content?.bg)} /> 84 + <p>{category}</p>] | {format(new Date(), "MMM d, yyyy HH:mm zzz")} 85 + </div> 86 + <h1 87 + tw="text-6xl text-black text-left font-medium" 88 + style={{ lineClamp: 2, display: "block" }} 89 + > 90 + {title} 91 + </h1> 92 + <p 93 + tw="text-4xl text-slate-700 text-left" 94 + style={{ lineClamp: 2, display: "block" }} 95 + > 96 + {description} 97 + </p> 98 + </div> 99 + <p tw="font-medium text-xl text-slate-500 text-left">{footer}</p> 100 + </div> 101 + </div>, 102 + { 103 + ...SIZE, 104 + fonts: [ 105 + { 106 + name: "Font Mono", 107 + data: fontMonoRegular, 108 + style: "normal", 109 + weight: 400, 110 + }, 111 + { 112 + name: "Font Mono", 113 + data: fontMonoMedium, 114 + style: "normal", 115 + weight: 500, 116 + }, 117 + { 118 + name: "Font Mono", 119 + data: fontMonoBold, 120 + style: "normal", 121 + weight: 700, 122 + }, 123 + ], 124 + }, 125 + ); 126 + }
+1 -1
apps/web/src/content/cmdk.tsx
··· 272 272 </kbd> 273 273 </button> 274 274 <Dialog open={open} onOpenChange={setOpen}> 275 - <DialogContent className="overflow-hidden rounded-none p-0 font-mono shadow-2xl top-[15%] translate-y-0"> 275 + <DialogContent className="top-[15%] translate-y-0 overflow-hidden rounded-none p-0 font-mono shadow-2xl"> 276 276 <DialogTitle className="sr-only">Search</DialogTitle> 277 277 <Command 278 278 onKeyDown={(e) => {