Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

status check (#393)

* wip

* chore: filter include monitoring

* fix: include

authored by

Maximilian Kaske and committed by
GitHub
32aa3409 541b7708

+138 -7
+4
apps/web/src/app/status-page/[domain]/page.tsx
··· 13 13 import { Header } from "@/components/dashboard/header"; 14 14 import { IncidentList } from "@/components/status-page/incident-list"; 15 15 import { MonitorList } from "@/components/status-page/monitor-list"; 16 + import { StatusCheck } from "@/components/status-page/status-check"; 17 + import { getMonitorListData } from "@/lib/tb"; 18 + import { notEmpty } from "@/lib/utils"; 16 19 import { api } from "@/trpc/server"; 17 20 18 21 const url = ··· 58 61 /> 59 62 ) : ( 60 63 <> 64 + <StatusCheck incidents={page.incidents} monitors={page.monitors} /> 61 65 <MonitorList monitors={page.monitors} /> 62 66 <IncidentList 63 67 incidents={page.incidents}
+4
apps/web/src/components/icons.tsx
··· 1 1 import { 2 2 Activity, 3 + AlertTriangle, 3 4 Bell, 4 5 Bot, 5 6 Calendar, ··· 12 13 LayoutDashboard, 13 14 Link, 14 15 MessageCircle, 16 + Minus, 15 17 PanelTop, 16 18 Pencil, 17 19 Play, ··· 60 62 image: Image, 61 63 bell: Bell, 62 64 zap: Zap, 65 + "alert-triangle": AlertTriangle, 66 + minus: Minus, 63 67 discord: ({ ...props }: LucideProps) => ( 64 68 <svg viewBox="0 0 640 512" {...props}> 65 69 <path
+93
apps/web/src/components/status-page/status-check.tsx
··· 1 + import type { z } from "zod"; 2 + 3 + import type { 4 + selectIncidentsPageSchema, 5 + selectPublicMonitorSchema, 6 + } from "@openstatus/db/src/schema"; 7 + 8 + import { getResponseListData } from "@/lib/tb"; 9 + import type { StatusVariant } from "@/lib/tracker"; 10 + import { getStatus } from "@/lib/tracker"; 11 + import { cn, notEmpty } from "@/lib/utils"; 12 + import { Icons } from "../icons"; 13 + 14 + export async function StatusCheck({ 15 + incidents, 16 + monitors, 17 + }: { 18 + incidents: z.infer<typeof selectIncidentsPageSchema>; 19 + monitors: z.infer<typeof selectPublicMonitorSchema>[]; 20 + }) { 21 + const isIncident = incidents.some( 22 + (incident) => !["monitoring", "resolved"].includes(incident.status), 23 + ); 24 + 25 + const monitorsData = ( 26 + await Promise.all( 27 + monitors.map((monitor) => { 28 + return getResponseListData({ 29 + monitorId: String(monitor.id), 30 + limit: 10, 31 + }); 32 + }), 33 + ) 34 + ).filter(notEmpty); 35 + 36 + function calcStatus() { 37 + const { count, ok } = monitorsData.flat(1).reduce( 38 + (prev, curr) => { 39 + const isOk = curr.statusCode <= 299 && curr.statusCode >= 200; 40 + return { count: prev.count + 1, ok: prev.ok + (isOk ? 1 : 0) }; 41 + }, 42 + { count: 0, ok: 0 }, 43 + ); 44 + const status = getStatus(ok / count); 45 + return status; 46 + } 47 + 48 + const status = calcStatus(); 49 + const incident = { 50 + label: "Incident", 51 + variant: "incident", 52 + twColor: "text-yellow-500", 53 + twBgColor: "bg-yellow-500", 54 + } as const; 55 + 56 + const { label, variant, twBgColor } = isIncident ? incident : status; 57 + 58 + return ( 59 + <div className="flex flex-col items-center gap-2"> 60 + <div className="flex items-center gap-3"> 61 + <p className="text-lg font-semibold">{label}</p> 62 + <span 63 + className={cn("border-border rounded-full border p-2", twBgColor)} 64 + > 65 + <StatusIcon variant={variant} /> 66 + </span> 67 + </div> 68 + <p className="text-muted-foreground text-xs">Status Check</p> 69 + </div> 70 + ); 71 + } 72 + 73 + interface StatusIconProps { 74 + variant: StatusVariant | "incident"; 75 + className?: string; 76 + } 77 + 78 + function StatusIcon({ variant, className }: StatusIconProps) { 79 + const rootClassName = cn("h-5 w-5 text-background", className); 80 + const MinusIcon = Icons["minus"]; 81 + const CheckIcon = Icons["check"]; 82 + const AlertTriangleIcon = Icons["alert-triangle"]; 83 + if (variant === "incident") { 84 + return <AlertTriangleIcon className={rootClassName} />; 85 + } 86 + if (variant === "degraded") { 87 + return <MinusIcon className={rootClassName} />; 88 + } 89 + if (variant === "down") { 90 + return <MinusIcon className={rootClassName} />; 91 + } 92 + return <CheckIcon className={rootClassName} />; 93 + }
+37 -7
apps/web/src/lib/tracker.ts
··· 1 1 import type { Monitor } from "@openstatus/tinybird"; 2 2 3 + export type StatusVariant = "up" | "degraded" | "down" | "empty"; 4 + 5 + type GetStatusReturnType = { 6 + label: string; 7 + variant: StatusVariant; 8 + twColor: string; 9 + twBgColor: string; 10 + }; 11 + 3 12 /** 4 13 * Get the status of a monitor based on its ratio 5 14 * @param ratio 6 15 * @returns 7 16 */ 8 - export const getStatus = ( 9 - ratio: number, 10 - ): { label: string; variant: "up" | "degraded" | "down" | "empty" } => { 11 - if (isNaN(ratio)) return { label: "Missing", variant: "empty" }; 12 - if (ratio >= 0.98) return { label: "Operational", variant: "up" }; 13 - if (ratio >= 0.5) return { label: "Degraded", variant: "degraded" }; 14 - return { label: "Downtime", variant: "down" }; 17 + export const getStatus = (ratio: number): GetStatusReturnType => { 18 + if (isNaN(ratio)) 19 + return { 20 + label: "Missing", 21 + variant: "empty", 22 + twColor: "text-gray-500", 23 + twBgColor: "bg-gray-500", 24 + }; 25 + if (ratio >= 0.98) 26 + return { 27 + label: "Operational", 28 + variant: "up", 29 + twColor: "text-green-500", 30 + twBgColor: "bg-green-500", 31 + }; 32 + if (ratio >= 0.5) 33 + return { 34 + label: "Degraded", 35 + variant: "degraded", 36 + twColor: "text-yellow-500", 37 + twBgColor: "bg-yellow-500", 38 + }; 39 + return { 40 + label: "Downtime", 41 + variant: "down", 42 + twColor: "text-red-500", 43 + twBgColor: "bg-red-500", 44 + }; 15 45 }; 16 46 17 47 /**