Openstatus www.openstatus.dev

chore: lazy monday improvements (#1533)

* fix: badge

* fix: domain accessor

authored by

Maximilian Kaske and committed by
GitHub
e75719a8 d4544e95

+171 -1
+1
apps/dashboard/src/components/data-table/status-pages/columns.tsx
··· 65 65 }, 66 66 { 67 67 accessorKey: "domain", 68 + accessorFn: (row) => row.customDomain, 68 69 header: "Domain", 69 70 cell: ({ row }) => { 70 71 const value = row.getValue("domain");
+74
apps/status-page/src/app/(status-page)/[domain]/(public)/badge/route.tsx
··· 1 + import { ImageResponse } from "next/og"; 2 + import type { NextRequest } from "next/server"; 3 + 4 + import type { Status } from "@openstatus/react"; 5 + import { getStatus } from "@openstatus/react"; 6 + 7 + // Keep the `label` size within a maximum of 'Operational' to stay within the `SIZE` restriction 8 + const statusDictionary: Record<Status, { label: string; color: string }> = { 9 + operational: { 10 + label: "Operational", 11 + color: "bg-green-500", 12 + }, 13 + degraded_performance: { 14 + label: "Degraded", 15 + color: "bg-yellow-500", 16 + }, 17 + partial_outage: { 18 + label: "Outage", 19 + color: "bg-yellow-500", 20 + }, 21 + major_outage: { 22 + label: "Outage", 23 + color: "bg-red-500", 24 + }, 25 + unknown: { 26 + label: "Unknown", 27 + color: "bg-gray-500", 28 + }, 29 + incident: { 30 + label: "Incident", 31 + color: "bg-yellow-500", 32 + }, 33 + under_maintenance: { 34 + label: "Maintenance", 35 + color: "bg-blue-500", 36 + }, 37 + } as const; 38 + 39 + // const SIZE = { width: 120, height: 34 }; 40 + const SIZE: Record<string, { width: number; height: number }> = { 41 + sm: { width: 120, height: 34 }, 42 + md: { width: 160, height: 46 }, 43 + lg: { width: 200, height: 56 }, 44 + xl: { width: 240, height: 68 }, 45 + }; 46 + export async function GET( 47 + req: NextRequest, 48 + props: { params: Promise<{ domain: string }> }, 49 + ) { 50 + const params = await props.params; 51 + const { status } = await getStatus(params.domain); 52 + const theme = req.nextUrl.searchParams.get("theme"); 53 + const size = req.nextUrl.searchParams.get("size"); 54 + const s = SIZE[size ?? "sm"] ?? SIZE.sm; 55 + const { label, color } = statusDictionary[status]; 56 + const light = "border-gray-200 text-gray-700 bg-white"; 57 + const dark = "border-gray-800 text-gray-300 bg-gray-900"; 58 + 59 + return new ImageResponse( 60 + <div 61 + tw={`flex items-center justify-center rounded-md border px-3 py-1 62 + ${size === "sm" && "text-sm"}${size === "md" && "text-md"} ${ 63 + size === "lg" && "text-lg" 64 + } ${size === "xl" && "text-xl"} ${!size && "text-sm"} ${ 65 + theme === "dark" ? dark : light 66 + }`} 67 + style={{ ...s }} 68 + > 69 + {label} 70 + <div tw={`flex h-2 w-2 rounded-full ml-2 ${color}`} /> 71 + </div>, 72 + { ...s }, 73 + ); 74 + }
+95
apps/status-page/src/app/(status-page)/[domain]/(public)/badge/v2/route.ts
··· 1 + import type { NextRequest } from "next/server"; 2 + 3 + import type { Status } from "@openstatus/react"; 4 + import { getStatus } from "@openstatus/react"; 5 + 6 + const statusDictionary: Record<Status, { label: string; hexColor: string }> = { 7 + operational: { 8 + label: "All Systems Operational", 9 + hexColor: "#10b981", 10 + }, 11 + degraded_performance: { 12 + label: "Degraded Performance", 13 + hexColor: "#f59e0b", 14 + }, 15 + partial_outage: { 16 + label: "Partial Outage", 17 + hexColor: "#f59e0b", 18 + }, 19 + major_outage: { 20 + label: "Major Outage", 21 + hexColor: "#ef4444", 22 + }, 23 + unknown: { 24 + label: "Unknown", 25 + hexColor: "#6b7280", 26 + }, 27 + incident: { 28 + label: "Ongoing Incident", 29 + hexColor: "#f59e0b", 30 + }, 31 + under_maintenance: { 32 + label: "Under Maintenance", 33 + hexColor: "#3b82f6", 34 + }, 35 + } as const; 36 + 37 + const SIZE: Record< 38 + string, 39 + { 40 + height: number; 41 + padding: number; 42 + gap: number; 43 + radius: number; 44 + fontSize: number; 45 + } 46 + > = { 47 + sm: { height: 34, padding: 8, gap: 12, radius: 4, fontSize: 12 }, 48 + md: { height: 46, padding: 8, gap: 12, radius: 4, fontSize: 14 }, 49 + lg: { height: 56, padding: 12, gap: 16, radius: 6, fontSize: 16 }, 50 + xl: { height: 68, padding: 12, gap: 16, radius: 6, fontSize: 18 }, 51 + }; 52 + 53 + function getTextWidth(text: string, fontSize: number): number { 54 + const monoCharWidthRatio = 0.6; 55 + return text.length * monoCharWidthRatio * fontSize; 56 + } 57 + 58 + export async function GET( 59 + req: NextRequest, 60 + props: { params: Promise<{ domain: string }> }, 61 + ) { 62 + const params = await props.params; 63 + const { status } = await getStatus(params.domain); 64 + const theme = req.nextUrl.searchParams.get("theme") ?? "light"; 65 + const variant = req.nextUrl.searchParams.get("variant") ?? "default"; 66 + const size = req.nextUrl.searchParams.get("size") ?? "sm"; 67 + 68 + const { height, padding, gap, radius, fontSize } = SIZE[size] ?? SIZE.sm; 69 + const { label, hexColor } = statusDictionary[status]; 70 + const textWidth = getTextWidth(label, fontSize); 71 + const width = Math.ceil(padding + textWidth + gap + radius * 2 + padding); 72 + 73 + const textColor = theme === "dark" ? "#d1d5db" : "#374151"; 74 + const bgColor = theme === "dark" ? "#111827" : "#ffffff"; 75 + const borderColor = variant === "outline" ? "#d1d5db" : "transparent"; 76 + 77 + const svg = ` 78 + <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"> 79 + <rect x="0.5" y="0.5" width="${width - 1}" height="${ 80 + height - 1 81 + }" fill="${bgColor}" stroke="${borderColor}" stroke-width="1" rx="${radius}" ry="${radius}" /> 82 + <text x="${padding}" y="50%" dominant-baseline="middle" 83 + font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" font-size="${fontSize}" font-weight="600" fill="${textColor}"> 84 + ${label} 85 + </text> 86 + <circle cx="${width - padding - radius}" cy="${ 87 + height / 2 88 + }" r="${radius}" fill="${hexColor}"/> 89 + </svg> 90 + `; 91 + 92 + return new Response(svg, { 93 + headers: { "Content-Type": "image/svg+xml" }, 94 + }); 95 + }
+1 -1
apps/web/next.config.js
··· 108 108 }, 109 109 { 110 110 source: 111 - "/:path((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|feed|events|monitors|protected|verify).*)", 111 + "/:path((?!api|assets|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|badge|feed|events|monitors|protected|verify).*)", 112 112 has: [ 113 113 { type: "cookie", key: "sp_mode", value: "new" }, 114 114 {