Openstatus www.openstatus.dev

feat: svg badge v2 (#1314)

* feat: svg badge v2

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

authored by

Maximilian Kaske
autofix-ci[bot]
and committed by
GitHub
dbac8cd4 dc83c168

+168 -13
+75
apps/docs/src/content/docs/tools/status-badge.mdx
··· 1 + --- 2 + title: Status Badge 3 + description: "Easily embed a badge into your website" 4 + --- 5 + 6 + ## PNG Badge 7 + 8 + ```html 9 + <img src='https://[slug].openstatus.dev/badge'> 10 + ``` 11 + 12 + This will return a PNG image with the status of your page. It will look like this: 13 + <img src='https://status.openstatus.dev/badge' /> 14 + 15 + You can customize the theme by adding `?theme=dark` and the size of it by adding `?size=lg`. 16 + 17 + Supported themes: 18 + - `dark` 19 + - `light` (default) 20 + 21 + Supported sizes: 22 + - `sm` (default) 23 + - `md` 24 + - `lg` 25 + - `xl` 26 + 27 + It will display the following labels based on the current status of the status page: 28 + 29 + ``` 30 + Operational 31 + Degraded 32 + Outage 33 + Incident 34 + Maintenance 35 + Unknown 36 + ``` 37 + 38 + ## SVG Badge (v2) 39 + 40 + ```html 41 + <img src='https://[slug].openstatus.dev/badge/v2'> 42 + ``` 43 + 44 + This will return a PNG image with the status of your page. It will look like this: 45 + <img src='https://status.openstatus.dev/badge/v2' /> 46 + 47 + You can customize the theme by adding `?theme=dark` and the size of it by adding `?size=lg`. 48 + 49 + Supported themes: 50 + - `dark` 51 + - `light` (default) 52 + 53 + Supported sizes: 54 + - `sm` (default) 55 + - `md` 56 + - `lg` 57 + - `xl` 58 + 59 + Compared to the PNG badge, we let you define the `border` and `border-radius` of the SVG badge and the font is mono. 60 + 61 + It will display the following labels based on the current status of the status page: 62 + 63 + ``` 64 + All Systems Operational 65 + Degraded Performance 66 + Partial Outage 67 + Major Outage 68 + Ongoing Incident 69 + Under Maintenance 70 + Unknown 71 + ``` 72 + 73 + --- 74 + 75 + Read more on which status is being shown [here](/tools/status-widget/#how-does-it-work).
+1 -12
apps/docs/src/content/docs/tools/status-widget.mdx
··· 287 287 --- 288 288 289 289 <StatusWidget slug="monitor-slug-here" /> 290 - ``` 291 - ### SVG Badge 292 - 293 - You can also use the SVG badge to display the status of your page. 294 - 295 - ```html 296 - <img src='https://[slug].openstatus.dev/badge'> 297 - ``` 298 - This will return an SVG image with the status of your page. It will look like this: 299 - <img src='https://status.openstatus.dev/badge' /> 300 - 301 - 290 + ```
+1 -1
apps/web/src/app/status-page/[domain]/badge/route.tsx
··· 32 32 }, 33 33 under_maintenance: { 34 34 label: "Maintenance", 35 - color: "bg-gray-500", 35 + color: "bg-blue-500", 36 36 }, 37 37 } as const; 38 38
+91
apps/web/src/app/status-page/[domain]/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 size = req.nextUrl.searchParams.get("size") ?? "sm"; 66 + 67 + const { height, padding, gap, radius, fontSize } = SIZE[size] ?? SIZE.sm; 68 + const { label, hexColor } = statusDictionary[status]; 69 + const textWidth = getTextWidth(label, fontSize); 70 + const width = Math.ceil(padding + textWidth + gap + radius * 2 + padding); 71 + 72 + const textColor = theme === "dark" ? "#d1d5db" : "#374151"; 73 + const bgColor = theme === "dark" ? "#111827" : "#ffffff"; 74 + 75 + const svg = ` 76 + <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"> 77 + <rect width="${width}" height="${height}" fill="${bgColor}"/> 78 + <text x="${padding}" y="50%" dominant-baseline="middle" 79 + font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" font-size="${fontSize}" font-weight="600" fill="${textColor}"> 80 + ${label} 81 + </text> 82 + <circle cx="${width - padding - radius}" cy="${ 83 + height / 2 84 + }" r="${radius}" fill="${hexColor}"/> 85 + </svg> 86 + `; 87 + 88 + return new Response(svg, { 89 + headers: { "Content-Type": "image/svg+xml" }, 90 + }); 91 + }