Openstatus www.openstatus.dev

๐ŸŒ koyeb (#1404)

* ๐ŸŒ

* ci: apply automated fixes

* ๐ŸŒ

* ๐Ÿค—

* ๐Ÿค—

* ๐ŸŒ koyeb

* ci: apply automated fixes

* ๐Ÿ”ฅ

* ๐ŸŒ

* ๐Ÿš€

* ci: apply automated fixes

* ๐Ÿ˜‚

* ci: apply automated fixes

* ๐Ÿ˜ญ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ci: apply automated fixes

* ๐Ÿ˜ฑ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ๐Ÿ”ฅ

* ci: apply automated fixes

* hore: form-regions-provider-icon

* fix: code label

* chore: play checker

* fix: tsc

* fix: mock

* fix: tsc and stuff

* fix: dashboard regions

* fix: spacing

* chore: extract icon cloud provider component

---------

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

authored by

Thibault Le Ouay
autofix-ci[bot]
Maximilian Kaske
and committed by
GitHub
fc7b170c 3a180cce

+1052 -424
+15 -4
apps/checker/cmd/main.go
··· 31 31 }() 32 32 33 33 // environment variables. 34 - flyRegion := env("FLY_REGION", env("REGION", "local")) 34 + var region string 35 35 cronSecret := env("CRON_SECRET", "") 36 36 tinyBirdToken := env("TINYBIRD_TOKEN", "") 37 37 logLevel := env("LOG_LEVEL", "warn") 38 38 cloudProvider := env("CLOUD_PROVIDER", "fly") 39 39 40 + switch cloudProvider { 41 + case "fly": 42 + region = env("FLY_REGION", env("REGION", "local")) 43 + 44 + case "koyeb": 45 + region = fmt.Sprintf("koyeb_%s", env("KOYEB_REGION", env("REGION", "local"))) 46 + 47 + case "railway": 48 + region = fmt.Sprintf("railway_%s", env("RAILWAY_REPLICA_REGION", env("REGION", "local"))) 49 + default: 50 + log.Fatal().Msgf("unsupported cloud provider: %s", cloudProvider) 51 + } 40 52 logger.Configure(logLevel) 41 53 42 - // packages. 43 54 httpClient := &http.Client{ 44 55 Timeout: 45 * time.Second, 45 56 } ··· 51 62 h := &handlers.Handler{ 52 63 Secret: cronSecret, 53 64 CloudProvider: cloudProvider, 54 - Region: flyRegion, 65 + Region: region, 55 66 TbClient: tinybirdClient, 56 67 } 57 68 ··· 63 74 router.POST("/tcp/:region", h.TCPHandlerRegion) 64 75 65 76 router.GET("/health", func(c *gin.Context) { 66 - c.JSON(http.StatusOK, gin.H{"message": "pong", "fly_region": flyRegion}) 77 + c.JSON(http.StatusOK, gin.H{"message": "pong", "region": region, "provider": cloudProvider}) 67 78 }) 68 79 69 80 httpServer := &http.Server{
+1
apps/checker/handlers/checker.go
··· 65 65 } 66 66 } 67 67 68 + 68 69 var req request.HttpCheckerRequest 69 70 if err := c.ShouldBindJSON(&req); err != nil { 70 71 log.Ctx(ctx).Error().Err(err).Msg("failed to decode checker request")
+3 -5
apps/checker/handlers/tcp.go
··· 86 86 trigger = req.Trigger 87 87 } 88 88 89 - 90 89 var response checker.TCPResponse 91 90 92 91 var retry int 93 - if req.Retry == 0 { 92 + if req.Retry == 0 { 94 93 retry = int(req.Retry) 95 94 } else { 96 95 retry = 3 ··· 123 122 break 124 123 } 125 124 126 - 127 125 id, err := uuid.NewV7() 128 126 if err != nil { 129 127 return fmt.Errorf("error while generating uuid %w", err) 130 128 } 131 129 132 130 data := TCPData{ 133 - ID: id.String(), 131 + ID: id.String(), 134 132 WorkspaceID: workspaceId, 135 133 Timestamp: res.TCPStart, 136 134 Error: 0, ··· 206 204 return 207 205 } 208 206 data := TCPData{ 209 - ID: id.String(), 207 + ID: id.String(), 210 208 WorkspaceID: workspaceId, 211 209 CronTimestamp: req.CronTimestamp, 212 210 ErrorMessage: err.Error(),
+3 -3
apps/dashboard/src/app/(dashboard)/monitors/[id]/logs/search-params.ts
··· 1 1 import { PERIODS, STATUS, TRIGGER } from "@/data/metrics.client"; 2 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 2 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 3 import { endOfDay } from "date-fns"; 4 4 import { startOfDay } from "date-fns"; 5 5 import { ··· 13 13 14 14 export const searchParamsParsers = { 15 15 period: parseAsStringLiteral(PERIODS).withDefault("1d"), 16 - regions: parseAsArrayOf(parseAsStringLiteral(flyRegions)).withDefault( 16 + regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)).withDefault( 17 17 // FIXME: readonly 18 - flyRegions as unknown as (typeof flyRegions)[number][], 18 + monitorRegions as unknown as (typeof monitorRegions)[number][], 19 19 ), 20 20 status: parseAsStringLiteral(STATUS), 21 21 trigger: parseAsStringLiteral(TRIGGER),
+3 -3
apps/dashboard/src/app/(dashboard)/monitors/[id]/overview/search-params.ts
··· 1 1 import { INTERVALS } from "@/data/metrics.client"; 2 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 2 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 3 import { 4 4 createSearchParamsCache, 5 5 parseAsArrayOf, ··· 12 12 13 13 export const searchParamsParsers = { 14 14 period: parseAsStringLiteral(PERIOD).withDefault("1d"), 15 - regions: parseAsArrayOf(parseAsStringLiteral(flyRegions)).withDefault( 15 + regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)).withDefault( 16 16 // FIXME: readonly 17 - flyRegions as unknown as (typeof flyRegions)[number][], 17 + monitorRegions as unknown as (typeof monitorRegions)[number][], 18 18 ), 19 19 percentile: parseAsStringLiteral(PERCENTILE).withDefault("p50"), 20 20 interval: parseAsNumberLiteral(INTERVALS).withDefault(30),
+2 -2
apps/dashboard/src/components/chart/chart-area-latency.tsx
··· 20 20 } from "@/components/ui/chart"; 21 21 import { type PERCENTILES, mapLatency } from "@/data/metrics.client"; 22 22 import { useTRPC } from "@/lib/trpc/client"; 23 - import type { flyRegions } from "@openstatus/db/src/schema/constants"; 23 + import type { monitorRegions } from "@openstatus/db/src/schema/constants"; 24 24 import { useQuery } from "@tanstack/react-query"; 25 25 import { ChartTooltipNumber } from "./chart-tooltip-number"; 26 26 ··· 46 46 percentile: (typeof PERCENTILES)[number]; 47 47 period: "1d" | "7d" | "14d"; 48 48 type: "http" | "tcp"; 49 - regions: (typeof flyRegions)[number][]; 49 + regions: (typeof monitorRegions)[number][]; 50 50 }) { 51 51 const trpc = useTRPC(); 52 52
+2 -2
apps/dashboard/src/components/chart/chart-area-timing-phases.tsx
··· 24 24 mapTimingPhases, 25 25 } from "@/data/metrics.client"; 26 26 import { useTRPC } from "@/lib/trpc/client"; 27 - import type { flyRegions } from "@openstatus/db/src/schema/constants"; 27 + import type { monitorRegions } from "@openstatus/db/src/schema/constants"; 28 28 import { useQuery } from "@tanstack/react-query"; 29 29 import { 30 30 ChartTooltipNumber, ··· 70 70 period: "1d" | "7d" | "14d"; 71 71 percentile: (typeof PERCENTILES)[number]; 72 72 interval: (typeof INTERVALS)[number]; 73 - regions: (typeof flyRegions)[number][]; 73 + regions: (typeof monitorRegions)[number][]; 74 74 type: "http"; 75 75 }) { 76 76 const trpc = useTRPC();
+42 -38
apps/dashboard/src/components/chart/chart-line-regions.tsx
··· 13 13 import { regionColors } from "@/data/regions"; 14 14 import { useIsMobile } from "@/hooks/use-mobile"; 15 15 import { cn } from "@/lib/utils"; 16 - import { flyRegionsDict } from "@openstatus/utils"; 16 + import { regionDict } from "@openstatus/utils"; 17 17 import { ChartTooltipNumber } from "./chart-tooltip-number"; 18 18 19 19 const chartConfig = { 20 - ams: { label: flyRegionsDict.ams.location, color: regionColors.ams }, 21 - arn: { label: flyRegionsDict.arn.location, color: regionColors.arn }, 22 - atl: { label: flyRegionsDict.atl.location, color: regionColors.atl }, 23 - bog: { label: flyRegionsDict.bog.location, color: regionColors.bog }, 24 - bom: { label: flyRegionsDict.bom.location, color: regionColors.bom }, 25 - bos: { label: flyRegionsDict.bos.location, color: regionColors.bos }, 26 - cdg: { label: flyRegionsDict.cdg.location, color: regionColors.cdg }, 27 - den: { label: flyRegionsDict.den.location, color: regionColors.den }, 28 - dfw: { label: flyRegionsDict.dfw.location, color: regionColors.dfw }, 29 - ewr: { label: flyRegionsDict.ewr.location, color: regionColors.ewr }, 30 - eze: { label: flyRegionsDict.eze.location, color: regionColors.eze }, 31 - fra: { label: flyRegionsDict.fra.location, color: regionColors.fra }, 32 - gdl: { label: flyRegionsDict.gdl.location, color: regionColors.gdl }, 33 - gig: { label: flyRegionsDict.gig.location, color: regionColors.gig }, 34 - gru: { label: flyRegionsDict.gru.location, color: regionColors.gru }, 35 - hkg: { label: flyRegionsDict.hkg.location, color: regionColors.hkg }, 36 - iad: { label: flyRegionsDict.iad.location, color: regionColors.iad }, 37 - jnb: { label: flyRegionsDict.jnb.location, color: regionColors.jnb }, 38 - lax: { label: flyRegionsDict.lax.location, color: regionColors.lax }, 39 - lhr: { label: flyRegionsDict.lhr.location, color: regionColors.lhr }, 40 - mad: { label: flyRegionsDict.mad.location, color: regionColors.mad }, 41 - mia: { label: flyRegionsDict.mia.location, color: regionColors.mia }, 42 - nrt: { label: flyRegionsDict.nrt.location, color: regionColors.nrt }, 43 - ord: { label: flyRegionsDict.ord.location, color: regionColors.ord }, 44 - otp: { label: flyRegionsDict.otp.location, color: regionColors.otp }, 45 - phx: { label: flyRegionsDict.phx.location, color: regionColors.phx }, 46 - qro: { label: flyRegionsDict.qro.location, color: regionColors.qro }, 47 - scl: { label: flyRegionsDict.scl.location, color: regionColors.scl }, 48 - sjc: { label: flyRegionsDict.sjc.location, color: regionColors.sjc }, 49 - sea: { label: flyRegionsDict.sea.location, color: regionColors.sea }, 50 - sin: { label: flyRegionsDict.sin.location, color: regionColors.sin }, 51 - syd: { label: flyRegionsDict.syd.location, color: regionColors.syd }, 52 - waw: { label: flyRegionsDict.waw.location, color: regionColors.waw }, 53 - yul: { label: flyRegionsDict.yul.location, color: regionColors.yul }, 54 - yyz: { label: flyRegionsDict.yyz.location, color: regionColors.yyz }, 20 + ams: { label: regionDict.ams.location, color: regionColors.ams }, 21 + arn: { label: regionDict.arn.location, color: regionColors.arn }, 22 + atl: { label: regionDict.atl.location, color: regionColors.atl }, 23 + bog: { label: regionDict.bog.location, color: regionColors.bog }, 24 + bom: { label: regionDict.bom.location, color: regionColors.bom }, 25 + bos: { label: regionDict.bos.location, color: regionColors.bos }, 26 + cdg: { label: regionDict.cdg.location, color: regionColors.cdg }, 27 + den: { label: regionDict.den.location, color: regionColors.den }, 28 + dfw: { label: regionDict.dfw.location, color: regionColors.dfw }, 29 + ewr: { label: regionDict.ewr.location, color: regionColors.ewr }, 30 + eze: { label: regionDict.eze.location, color: regionColors.eze }, 31 + fra: { label: regionDict.fra.location, color: regionColors.fra }, 32 + gdl: { label: regionDict.gdl.location, color: regionColors.gdl }, 33 + gig: { label: regionDict.gig.location, color: regionColors.gig }, 34 + gru: { label: regionDict.gru.location, color: regionColors.gru }, 35 + hkg: { label: regionDict.hkg.location, color: regionColors.hkg }, 36 + iad: { label: regionDict.iad.location, color: regionColors.iad }, 37 + jnb: { label: regionDict.jnb.location, color: regionColors.jnb }, 38 + lax: { label: regionDict.lax.location, color: regionColors.lax }, 39 + lhr: { label: regionDict.lhr.location, color: regionColors.lhr }, 40 + mad: { label: regionDict.mad.location, color: regionColors.mad }, 41 + mia: { label: regionDict.mia.location, color: regionColors.mia }, 42 + nrt: { label: regionDict.nrt.location, color: regionColors.nrt }, 43 + ord: { label: regionDict.ord.location, color: regionColors.ord }, 44 + otp: { label: regionDict.otp.location, color: regionColors.otp }, 45 + phx: { label: regionDict.phx.location, color: regionColors.phx }, 46 + qro: { label: regionDict.qro.location, color: regionColors.qro }, 47 + scl: { label: regionDict.scl.location, color: regionColors.scl }, 48 + sjc: { label: regionDict.sjc.location, color: regionColors.sjc }, 49 + sea: { label: regionDict.sea.location, color: regionColors.sea }, 50 + sin: { label: regionDict.sin.location, color: regionColors.sin }, 51 + syd: { label: regionDict.syd.location, color: regionColors.syd }, 52 + waw: { label: regionDict.waw.location, color: regionColors.waw }, 53 + yul: { label: regionDict.yul.location, color: regionColors.yul }, 54 + yyz: { label: regionDict.yyz.location, color: regionColors.yyz }, 55 + koyeb_fra: { label: regionDict.ams.location, color: regionColors.koyeb_fra }, 56 + koyeb_par: { label: regionDict.arn.location, color: regionColors.koyeb_par }, 57 + koyeb_sfo: { label: regionDict.atl.location, color: regionColors.koyeb_sfo }, 58 + koyeb_sin: { label: regionDict.bog.location, color: regionColors.koyeb_sin }, 59 + koyeb_tyo: { label: regionDict.bom.location, color: regionColors.koyeb_tyo }, 60 + koyeb_was: { label: regionDict.bos.location, color: regionColors.koyeb_was }, 55 61 } satisfies ChartConfig; 56 62 57 63 export type TrendPoint = { ··· 70 76 }) { 71 77 const isMobile = useIsMobile(); 72 78 const trendData = data ?? []; 73 - 74 - console.log({ data, regions }); 75 79 76 80 const chartData = trendData.map((d) => ({ 77 81 ...d,
+21
apps/dashboard/src/components/common/icon-cloud-provider.tsx
··· 1 + import { cn } from "@/lib/utils"; 2 + import { Fly, Koyeb, Railway } from "@openstatus/icons"; 3 + import { Globe } from "lucide-react"; 4 + 5 + export function IconCloudProvider({ 6 + provider, 7 + className, 8 + }: React.ComponentProps<"svg"> & { 9 + provider: string; 10 + }) { 11 + switch (provider) { 12 + case "fly": 13 + return <Fly className={cn("size-4", className)} />; 14 + case "koyeb": 15 + return <Koyeb className={cn("size-4", className)} />; 16 + case "railway": 17 + return <Railway className={cn("size-4", className)} />; 18 + default: 19 + return <Globe className={cn("size-4", className)} />; 20 + } 21 + }
+11 -4
apps/dashboard/src/components/controls-search/command-region.tsx
··· 1 1 "use client"; 2 2 3 + import { IconCloudProvider } from "@/components/common/icon-cloud-provider"; 3 4 import { Link } from "@/components/common/link"; 4 5 import { 5 6 BillingOverlay, ··· 59 60 </PopoverTrigger> 60 61 <PopoverContent 61 62 align="start" 62 - className="relative w-[200px] overflow-hidden p-0" 63 + className="relative w-[250px] overflow-hidden p-0" 63 64 > 64 65 <Command> 65 66 <CommandInput placeholder="Search region..." disabled={limited} /> ··· 122 123 ); 123 124 }} 124 125 > 125 - <span className="mr-1">{region.flag}</span> 126 - {region.code} 127 - <span className="ml-1 truncate text-muted-foreground text-xs"> 126 + <span>{region.flag}</span> 127 + <IconCloudProvider 128 + provider={region.provider} 129 + className="size-3" 130 + /> 131 + <span className="font-mono"> 132 + {region.code.replace(/(koyeb_|railway_|fly_)/g, "")} 133 + </span> 134 + <span className="truncate text-muted-foreground text-xs"> 128 135 {region.location} 129 136 </span> 130 137 <Check
+2 -2
apps/dashboard/src/components/data-table/response-logs/columns.tsx
··· 17 17 import { getStatusCodeVariant, textColors } from "@/data/status-codes"; 18 18 import { cn } from "@/lib/utils"; 19 19 import type { RouterOutputs } from "@openstatus/api"; 20 - import { flyRegionsDict } from "@openstatus/utils"; 20 + import { regionDict } from "@openstatus/utils"; 21 21 import { HoverCardPortal } from "@radix-ui/react-hover-card"; 22 22 import type { ColumnDef } from "@tanstack/react-table"; 23 23 import { Clock, Workflow } from "lucide-react"; ··· 94 94 return <div className="text-muted-foreground">-</div>; 95 95 } 96 96 97 - const regionConfig = flyRegionsDict[value as keyof typeof flyRegionsDict]; 97 + const regionConfig = regionDict[value as keyof typeof regionDict]; 98 98 return regionConfig.location; 99 99 }, 100 100 enableSorting: false,
+3 -3
apps/dashboard/src/components/data-table/response-logs/data-table-basics.tsx
··· 15 15 import { formatMilliseconds, formatPercentage } from "@/lib/formatter"; 16 16 import { cn } from "@/lib/utils"; 17 17 import type { RouterOutputs } from "@openstatus/api"; 18 - import { flyRegionsDict } from "@openstatus/utils"; 18 + import { regionDict } from "@openstatus/utils"; 19 19 import { Braces, TableProperties } from "lucide-react"; 20 20 21 21 type ResponseLog = RouterOutputs["tinybird"]["get"]["data"][number]; ··· 37 37 trigger?: "cron" | "api" | "test" | null; 38 38 }; 39 39 }) { 40 - const regionConfig = flyRegionsDict[data.region]; 40 + const regionConfig = regionDict[data.region]; 41 41 return ( 42 42 <Table className="table-fixed"> 43 43 <colgroup> ··· 294 294 trigger?: "cron" | "api" | "test" | null; 295 295 }; 296 296 }) { 297 - const regionConfig = flyRegionsDict[data.region]; 297 + const regionConfig = regionDict[data.region]; 298 298 return ( 299 299 <Table className="table-fixed"> 300 300 <colgroup>
+9 -5
apps/dashboard/src/components/data-table/response-logs/regions/columns.tsx
··· 12 12 } from "@/components/ui/tooltip"; 13 13 import type { RegionMetric } from "@/data/region-metrics"; 14 14 import { getActions } from "@/data/region-metrics.client"; 15 - import { flyRegionsDict } from "@openstatus/utils"; 15 + import { regionDict } from "@openstatus/utils"; 16 16 import type { ColumnDef } from "@tanstack/react-table"; 17 17 // import { toast } from "sonner"; 18 18 import { useRouter } from "next/navigation"; ··· 28 28 cell: ({ row }) => { 29 29 const value = row.getValue("region"); 30 30 if (typeof value === "string") { 31 - const region = flyRegionsDict[value as keyof typeof flyRegionsDict]; 31 + const region = regionDict[value as keyof typeof regionDict]; 32 32 return ( 33 33 <TooltipProvider> 34 34 <Tooltip> 35 - <TooltipTrigger className="h-[50px]"> 36 - {region.flag} {region.code} 35 + <TooltipTrigger className="flex h-[50px] items-center gap-1"> 36 + {region.flag}{" "} 37 + {region.code.replace(/(koyeb_|railway_|fly_)/g, "")} 37 38 </TooltipTrigger> 38 - <TooltipContent side="left">{region.location}</TooltipContent> 39 + <TooltipContent side="left"> 40 + {region.location} -{" "} 41 + <span className="capitalize">{region.provider}</span> 42 + </TooltipContent> 39 43 </Tooltip> 40 44 </TooltipProvider> 41 45 );
+27 -5
apps/dashboard/src/components/forms/monitor/form-scheduling-regions.tsx
··· 25 25 import { cn } from "@/lib/utils"; 26 26 import { zodResolver } from "@hookform/resolvers/zod"; 27 27 import { 28 - type MonitorFlyRegion, 28 + type Region, 29 29 monitorPeriodicity, 30 30 } from "@openstatus/db/src/schema/constants"; 31 31 import { useState, useTransition } from "react"; ··· 33 33 import { toast } from "sonner"; 34 34 import { z } from "zod"; 35 35 36 + import { IconCloudProvider } from "@/components/common/icon-cloud-provider"; 36 37 import { Note, NoteButton } from "@/components/common/note"; 37 38 import { UpgradeDialog } from "@/components/dialogs/upgrade"; 39 + import { 40 + Tooltip, 41 + TooltipContent, 42 + TooltipProvider, 43 + TooltipTrigger, 44 + } from "@/components/ui/tooltip"; 38 45 import { useTRPC } from "@/lib/trpc/client"; 39 46 import { groupByContinent } from "@openstatus/utils"; 40 47 import { useQuery } from "@tanstack/react-query"; ··· 239 246 (region) => 240 247 !r 241 248 .map(({ code }) => code) 242 - .includes( 243 - region as MonitorFlyRegion, 244 - ), 249 + .includes(region as Region), 245 250 ), 246 251 ); 247 252 } ··· 299 304 className="w-full truncate font-mono font-normal text-sm" 300 305 > 301 306 <span className="text-nowrap"> 302 - {region.code} {region.flag} 307 + {region.code.replace( 308 + /(koyeb_|railway_|fly_)/g, 309 + "", 310 + )}{" "} 311 + {region.flag} 303 312 </span> 304 313 <span className="truncate font-normal text-muted-foreground text-xs leading-[inherit]"> 305 314 {region.location} 306 315 </span> 316 + <TooltipProvider> 317 + <Tooltip> 318 + <TooltipTrigger type="button"> 319 + <IconCloudProvider 320 + provider={region.provider} 321 + className="size-3" 322 + /> 323 + </TooltipTrigger> 324 + <TooltipContent className="capitalize"> 325 + {region.provider} 326 + </TooltipContent> 327 + </Tooltip> 328 + </TooltipProvider> 307 329 </FormLabel> 308 330 </FormItem> 309 331 );
+2 -2
apps/dashboard/src/components/metric/global-uptime/section.tsx
··· 18 18 formatNumber, 19 19 formatPercentage, 20 20 } from "@/lib/formatter"; 21 - import type { flyRegions } from "@openstatus/db/src/schema/constants"; 21 + import type { monitorRegions } from "@openstatus/db/src/schema/constants"; 22 22 import { formatDistanceToNow } from "date-fns"; 23 23 24 24 type Metric = { ··· 40 40 monitorId: string; 41 41 jobType: "http" | "tcp"; 42 42 period: "1d" | "7d" | "14d"; 43 - regions: (typeof flyRegions)[number][]; 43 + regions: (typeof monitorRegions)[number][]; 44 44 }) { 45 45 const trpc = useTRPC(); 46 46
+3 -2
apps/dashboard/src/data/metrics.client.ts
··· 3 3 import type { MetricCard } from "@/components/metric/metric-card"; 4 4 import { formatDateTime, formatMilliseconds } from "@/lib/formatter"; 5 5 import type { RouterOutputs } from "@openstatus/api"; 6 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 6 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 7 7 import type { RegionMetric } from "./region-metrics"; 8 8 import type { Region } from "./regions"; 9 9 10 10 export const STATUS = ["success", "error", "degraded"] as const; 11 11 export const PERIODS = ["1d", "7d", "14d"] as const; 12 - export const REGIONS = flyRegions as unknown as (typeof flyRegions)[number][]; 12 + export const REGIONS = 13 + monitorRegions as unknown as (typeof monitorRegions)[number][]; 13 14 export const PERCENTILES = ["p50", "p75", "p90", "p95", "p99"] as const; 14 15 export const INTERVALS = [5, 15, 30, 60, 120, 240, 480, 1440] as const; 15 16 export const TRIGGER = ["api", "cron"] as const;
+81
apps/dashboard/src/data/regions.ts
··· 4 4 location: "Amsterdam, Netherlands", 5 5 flag: "๐Ÿ‡ณ๐Ÿ‡ฑ", 6 6 continent: "Europe", 7 + provider: "Fly", 7 8 }, 8 9 { 9 10 code: "arn", 10 11 location: "Stockholm, Sweden", 11 12 flag: "๐Ÿ‡ธ๐Ÿ‡ช", 12 13 continent: "Europe", 14 + provider: "Fly", 13 15 }, 14 16 { 15 17 code: "atl", 16 18 location: "Atlanta, Georgia, USA", 17 19 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 18 20 continent: "North America", 21 + provider: "Fly", 19 22 }, 20 23 { 21 24 code: "bog", 22 25 location: "Bogotรก, Colombia", 23 26 flag: "๐Ÿ‡จ๐Ÿ‡ด", 24 27 continent: "South America", 28 + provider: "Fly", 25 29 }, 26 30 { 27 31 code: "bom", 28 32 location: "Mumbai, India", 29 33 flag: "๐Ÿ‡ฎ๐Ÿ‡ณ", 30 34 continent: "Asia", 35 + provider: "Fly", 31 36 }, 32 37 { 33 38 code: "bos", 34 39 location: "Boston, Massachusetts, USA", 35 40 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 36 41 continent: "North America", 42 + provider: "Fly", 37 43 }, 38 44 { 39 45 code: "cdg", 40 46 location: "Paris, France", 41 47 flag: "๐Ÿ‡ซ๐Ÿ‡ท", 42 48 continent: "Europe", 49 + provider: "Fly", 43 50 }, 44 51 { 45 52 code: "den", 46 53 location: "Denver, Colorado, USA", 47 54 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 48 55 continent: "North America", 56 + provider: "Fly", 49 57 }, 50 58 { 51 59 code: "dfw", 52 60 location: "Dallas, Texas, USA", 53 61 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 54 62 continent: "North America", 63 + provider: "Fly", 55 64 }, 56 65 { 57 66 code: "ewr", 58 67 location: "Secaucus, New Jersey, USA", 59 68 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 60 69 continent: "North America", 70 + provider: "Fly", 61 71 }, 62 72 { 63 73 code: "eze", 64 74 location: "Ezeiza, Argentina", 65 75 flag: "๐Ÿ‡ฆ๐Ÿ‡ท", 66 76 continent: "South America", 77 + provider: "Fly", 67 78 }, 68 79 { 69 80 code: "fra", 70 81 location: "Frankfurt, Germany", 71 82 flag: "๐Ÿ‡ฉ๐Ÿ‡ช", 72 83 continent: "Europe", 84 + provider: "Fly", 73 85 }, 74 86 { 75 87 code: "gdl", 76 88 location: "Guadalajara, Mexico", 77 89 flag: "๐Ÿ‡ฒ๐Ÿ‡ฝ", 78 90 continent: "North America", 91 + provider: "Fly", 79 92 }, 80 93 { 81 94 code: "gig", 82 95 location: "Rio de Janeiro, Brazil", 83 96 flag: "๐Ÿ‡ง๐Ÿ‡ท", 84 97 continent: "South America", 98 + provider: "Fly", 85 99 }, 86 100 { 87 101 code: "gru", 88 102 location: "Sao Paulo, Brazil", 89 103 flag: "๐Ÿ‡ง๐Ÿ‡ท", 90 104 continent: "South America", 105 + provider: "Fly", 91 106 }, 92 107 { 93 108 code: "hkg", 94 109 location: "Hong Kong, Hong Kong", 95 110 flag: "๐Ÿ‡ญ๐Ÿ‡ฐ", 96 111 continent: "Asia", 112 + provider: "Fly", 97 113 }, 98 114 { 99 115 code: "iad", 100 116 location: "Ashburn, Virginia, USA", 101 117 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 102 118 continent: "North America", 119 + provider: "Fly", 103 120 }, 104 121 { 105 122 code: "jnb", ··· 112 129 location: "Los Angeles, California, USA", 113 130 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 114 131 continent: "North America", 132 + provider: "Fly", 115 133 }, 116 134 { 117 135 code: "lhr", 118 136 location: "London, United Kingdom", 119 137 flag: "๐Ÿ‡ฌ๐Ÿ‡ง", 120 138 continent: "Europe", 139 + provider: "Fly", 121 140 }, 122 141 { 123 142 code: "mad", ··· 130 149 location: "Miami, Florida, USA", 131 150 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 132 151 continent: "North America", 152 + provider: "Fly", 133 153 }, 134 154 { 135 155 code: "nrt", 136 156 location: "Tokyo, Japan", 137 157 flag: "๐Ÿ‡ฏ๐Ÿ‡ต", 138 158 continent: "Asia", 159 + provider: "Fly", 139 160 }, 140 161 { 141 162 code: "ord", ··· 148 169 location: "Bucharest, Romania", 149 170 flag: "๐Ÿ‡ท๐Ÿ‡ด", 150 171 continent: "Europe", 172 + provider: "Fly", 151 173 }, 152 174 { 153 175 code: "phx", 154 176 location: "Phoenix, Arizona, USA", 155 177 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 156 178 continent: "North America", 179 + provider: "Fly", 157 180 }, 158 181 { 159 182 code: "qro", 160 183 location: "Querรฉtaro, Mexico", 161 184 flag: "๐Ÿ‡ฒ๐Ÿ‡ฝ", 162 185 continent: "North America", 186 + provider: "Fly", 163 187 }, 164 188 { 165 189 code: "scl", 166 190 location: "Santiago, Chile", 167 191 flag: "๐Ÿ‡จ๐Ÿ‡ฑ", 168 192 continent: "South America", 193 + provider: "Fly", 169 194 }, 170 195 { 171 196 code: "sjc", 172 197 location: "San Jose, California, USA", 173 198 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 174 199 continent: "North America", 200 + provider: "Fly", 175 201 }, 176 202 { 177 203 code: "sea", 178 204 location: "Seattle, Washington, USA", 179 205 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 180 206 continent: "North America", 207 + provider: "Fly", 181 208 }, 182 209 { 183 210 code: "sin", 184 211 location: "Singapore, Singapore", 185 212 flag: "๐Ÿ‡ธ๐Ÿ‡ฌ", 186 213 continent: "Asia", 214 + provider: "Fly", 187 215 }, 188 216 { 189 217 code: "syd", 190 218 location: "Sydney, Australia", 191 219 flag: "๐Ÿ‡ฆ๐Ÿ‡บ", 192 220 continent: "Oceania", 221 + provider: "Fly", 193 222 }, 194 223 { 195 224 code: "waw", 196 225 location: "Warsaw, Poland", 197 226 flag: "๐Ÿ‡ต๐Ÿ‡ฑ", 198 227 continent: "Europe", 228 + provider: "Fly", 199 229 }, 200 230 { 201 231 code: "yul", 202 232 location: "Montreal, Canada", 203 233 flag: "๐Ÿ‡จ๐Ÿ‡ฆ", 204 234 continent: "North America", 235 + provider: "Fly", 205 236 }, 206 237 { 207 238 code: "yyz", 208 239 location: "Toronto, Canada", 209 240 flag: "๐Ÿ‡จ๐Ÿ‡ฆ", 210 241 continent: "North America", 242 + provider: "Fly", 243 + }, 244 + { 245 + code: "koyeb_fra", 246 + location: "Frankfurt, Germany", 247 + flag: "๐Ÿ‡ฉ๐Ÿ‡ช", 248 + continent: "Europe", 249 + provider: "koyeb", 250 + }, 251 + { 252 + code: "koyeb_par", 253 + location: "Paris, France", 254 + flag: "๐Ÿ‡ซ๐Ÿ‡ท", 255 + continent: "Europe", 256 + provider: "koyeb", 257 + }, 258 + { 259 + code: "koyeb_sfo", 260 + location: "San Francisco, USA", 261 + flag: "๐Ÿ‡บ๐Ÿ‡ธ", 262 + continent: "North America", 263 + provider: "koyeb", 264 + }, 265 + { 266 + code: "koyeb_sin", 267 + location: "Singapore, Singapore", 268 + flag: "๐Ÿ‡ธ๐Ÿ‡ฌ", 269 + continent: "Asia", 270 + provider: "koyeb", 271 + }, 272 + { 273 + code: "koyeb_tyo", 274 + location: "Tokyo, Japan", 275 + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", 276 + continent: "Asia", 277 + provider: "koyeb", 278 + }, 279 + { 280 + code: "koyeb_was", 281 + location: "Washington, USA", 282 + flag: "๐Ÿ‡บ๐Ÿ‡ธ", 283 + continent: "North America", 284 + provider: "koyeb", 211 285 }, 212 286 ] as const; 213 287 ··· 261 335 waw: "hsl(0 0% 45.1%)", 262 336 yul: "hsl(25 5.3% 44.7%)", 263 337 yyz: "hsl(0 84.2% 60.2%)", 338 + 339 + koyeb_fra: "hsl(25 5.3% 44.7%)", 340 + koyeb_par: "hsl(25 5.3% 44.7%)", 341 + koyeb_sin: "hsl(25 5.3% 44.7%)", 342 + koyeb_sfo: "hsl(0 0% 45.1%)", 343 + koyeb_tyo: "hsl(0 0% 45.1%)", 344 + koyeb_was: "hsl(0 0% 45.1%)", 264 345 } satisfies Record<Region, string>;
+2 -2
apps/dashboard/src/data/response-logs.ts
··· 1 1 import type { RouterOutputs } from "@openstatus/api"; 2 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 2 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 3 import { startOfDay } from "date-fns"; 4 4 5 5 type ResponseLog = RouterOutputs["tinybird"]["list"]["data"][number]; ··· 23 23 transfer: 50, 24 24 }, 25 25 assertions: [], 26 - region: flyRegions[i], 26 + region: monitorRegions[i], 27 27 error: false, 28 28 timestamp: today.getTime() + i * 1000 * 60, 29 29 headers: {
+2 -2
apps/server/src/env.ts
··· 1 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 1 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 2 2 import { createEnv } from "@t3-oss/env-core"; 3 3 import { z } from "zod"; 4 4 ··· 9 9 TINY_BIRD_API_KEY: z.string().min(1), 10 10 UPSTASH_REDIS_REST_URL: z.string().min(1), 11 11 UPSTASH_REDIS_REST_TOKEN: z.string().min(1), 12 - FLY_REGION: z.enum(flyRegions), 12 + FLY_REGION: z.enum(monitorRegions), 13 13 CRON_SECRET: z.string(), 14 14 SCREENSHOT_SERVICE_URL: z.string(), 15 15 QSTASH_TOKEN: z.string(),
+6 -6
apps/server/src/routes/v1/monitors/schema.ts
··· 3 3 import { numberCompare, stringCompare } from "@openstatus/assertions"; 4 4 import { monitorJobTypes, monitorMethods } from "@openstatus/db/src/schema"; 5 5 import { 6 - flyRegions, 7 6 monitorPeriodicitySchema, 7 + monitorRegions, 8 8 } from "@openstatus/db/src/schema/constants"; 9 9 import { ZodError } from "zod"; 10 10 ··· 110 110 ]); 111 111 } 112 112 }, 113 - z.array(z.enum(flyRegions)), 113 + z.array(z.enum(monitorRegions)), 114 114 ) 115 115 .default([]) 116 116 .openapi({ ··· 252 252 jobType: z.literal("http"), 253 253 status: z.number(), 254 254 latency: z.number(), 255 - region: z.enum(flyRegions), 255 + region: z.enum(monitorRegions), 256 256 timestamp: z.number(), 257 257 timing: timingSchema, 258 258 body: z.string().optional().nullable(), ··· 267 267 export const TCPTriggerResult = z.object({ 268 268 jobType: z.literal("tcp"), 269 269 latency: z.number(), 270 - region: z.enum(flyRegions), 270 + region: z.enum(monitorRegions), 271 271 timestamp: z.number(), 272 272 timing: tcptimingSchema, 273 273 // check if it should be z.coerce.boolean()? ··· 286 286 monitorId: z.string().default(""), 287 287 url: z.string().optional(), 288 288 error: z.coerce.boolean().default(false), 289 - region: z.enum(flyRegions), 289 + region: z.enum(monitorRegions), 290 290 timestamp: z.number().int().optional(), 291 291 message: z.string().nullable().optional(), 292 292 timing: z ··· 343 343 description: "Whether the monitor is public", 344 344 default: false, 345 345 }), 346 - regions: z.array(z.enum(flyRegions)).openapi({ 346 + regions: z.array(z.enum(monitorRegions)).openapi({ 347 347 description: "Regions to run the request in", 348 348 }), 349 349 openTelemetry: z
+3 -2
apps/status-page/src/data/metrics.client.ts
··· 3 3 import type { MetricCard } from "@/components/content/metric-card"; 4 4 import { formatDateTime, formatMilliseconds } from "@/lib/formatter"; 5 5 import type { RouterOutputs } from "@openstatus/api"; 6 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 6 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 7 7 import type { RegionMetric } from "./region-metrics"; 8 8 import type { Region } from "./regions"; 9 9 10 10 export const STATUS = ["success", "error", "degraded"] as const; 11 11 export const PERIODS = ["1d", "7d", "14d"] as const; 12 - export const REGIONS = flyRegions as unknown as (typeof flyRegions)[number][]; 12 + export const REGIONS = 13 + monitorRegions as unknown as (typeof monitorRegions)[number][]; 13 14 export const PERCENTILES = ["p50", "p75", "p90", "p95", "p99"] as const; 14 15 export const INTERVALS = [5, 15, 30, 60, 120, 240, 480, 1440] as const; 15 16 export const TRIGGER = ["api", "cron"] as const;
+1
apps/web/package.json
··· 24 24 "@openstatus/emails": "workspace:*", 25 25 "@openstatus/error": "workspace:*", 26 26 "@openstatus/header-analysis": "workspace:*", 27 + "@openstatus/icons": "workspace:*", 27 28 "@openstatus/notification-discord": "workspace:*", 28 29 "@openstatus/notification-emails": "workspace:*", 29 30 "@openstatus/notification-ntfy": "workspace:*",
+7 -3
apps/web/src/app/(pages)/(content)/play/checker/[id]/page.tsx
··· 2 2 import Link from "next/link"; 3 3 import { redirect } from "next/navigation"; 4 4 5 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 5 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 6 6 import { Separator } from "@openstatus/ui"; 7 7 8 8 import { ··· 17 17 getCheckerDataById, 18 18 timestampFormatter, 19 19 } from "@/components/ping-response-analysis/utils"; 20 + import { mockCheckAllRegions } from "../api/mock"; 20 21 import { searchParamsCache } from "./search-params"; 21 22 22 23 interface Props { ··· 28 29 const searchParams = await props.searchParams; 29 30 const params = await props.params; 30 31 const { regions } = searchParamsCache.parse(searchParams); 31 - const selectedRegions = regions || [...flyRegions]; 32 + const selectedRegions = regions || [...monitorRegions]; 32 33 33 - const data = await getCheckerDataById(params.id); 34 + const data = 35 + process.env.NODE_ENV === "development" 36 + ? await mockCheckAllRegions() 37 + : await getCheckerDataById(params.id); 34 38 35 39 if (!data) redirect("/play/checker"); 36 40
+2 -2
apps/web/src/app/(pages)/(content)/play/checker/[id]/search-params.ts
··· 1 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 1 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 2 2 import { 3 3 createSearchParamsCache, 4 4 parseAsArrayOf, ··· 6 6 } from "nuqs/server"; 7 7 8 8 export const searchParamsParsers = { 9 - regions: parseAsArrayOf(parseAsStringLiteral(flyRegions)), 9 + regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)), 10 10 }; 11 11 12 12 export const searchParamsCache = createSearchParamsCache(searchParamsParsers);
+52 -16
apps/web/src/app/(pages)/(content)/play/checker/_components/checker-form.tsx
··· 45 45 } from "@/components/ping-response-analysis/utils"; 46 46 import { toast } from "@/lib/toast"; 47 47 import { notEmpty } from "@/lib/utils"; 48 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 49 - import { ArrowRight, ChevronRight, Gauge, Info, Loader } from "lucide-react"; 48 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 49 + import { Fly, Koyeb, Railway } from "@openstatus/icons"; 50 + import { regionDict } from "@openstatus/utils"; 51 + import { 52 + ArrowRight, 53 + ChevronRight, 54 + Gauge, 55 + Globe, 56 + Info, 57 + Loader, 58 + } from "lucide-react"; 50 59 import dynamic from "next/dynamic"; 51 60 import Link from "next/link"; 52 61 import { useQueryStates } from "nuqs"; ··· 173 182 if (_result) { 174 183 if (_result[0].state === "success") { 175 184 toast.loading( 176 - `Checking ${regionFormatter(_result[0].region, "long")} (${latencyFormatter(_result[0].latency)})`, 185 + `Checking ${regionFormatter( 186 + _result[0].region, 187 + "long", 188 + )} (${latencyFormatter(_result[0].latency)})`, 177 189 { 178 190 id: toastId, 179 191 }, ··· 322 334 <p className="w-[95px]"> 323 335 Region{" "} 324 336 <span className="font-normal text-xs tabular-nums"> 325 - ({result.length}/{flyRegions.length}) 337 + ({result.length}/{monitorRegions.length}) 326 338 </span> 327 339 </p> 328 340 {loading ? ( ··· 333 345 {id && 334 346 !loading && 335 347 result.length > 0 && 336 - result.length !== flyRegions.length ? ( 348 + result.length !== monitorRegions.length ? ( 337 349 <TooltipProvider> 338 350 <Tooltip> 339 351 <TooltipTrigger> ··· 363 375 {result.length > 0 ? ( 364 376 result 365 377 .filter((item) => item.state === "success") 366 - .map((item) => ( 367 - <TableRow key={item.region}> 368 - <TableCell className="flex items-center gap-2 font-medium"> 369 - {regionFormatter(item.region, "long")} 370 - <StatusDot value={item.status} /> 371 - </TableCell> 372 - <TableCell className="text-right"> 373 - {latencyFormatter(item.latency)} 374 - </TableCell> 375 - </TableRow> 376 - )) 378 + .map((item) => { 379 + const region = regionFormatter(item.region, "long"); 380 + const latency = latencyFormatter(item.latency); 381 + const r = regionDict[item.region]; 382 + return ( 383 + <TableRow key={item.region}> 384 + <TableCell className="flex items-center gap-2 font-medium"> 385 + <TooltipProvider> 386 + <Tooltip delayDuration={0}> 387 + <TooltipTrigger type="button"> 388 + {(() => { 389 + switch (r.provider) { 390 + case "fly": 391 + return <Fly className="size-4" />; 392 + case "railway": 393 + return <Railway className="size-4" />; 394 + case "koyeb": 395 + return <Koyeb className="size-4" />; 396 + default: 397 + return <Globe className="size-4" />; 398 + } 399 + })()} 400 + </TooltipTrigger> 401 + <TooltipContent className="capitalize"> 402 + {r.provider} 403 + </TooltipContent> 404 + </Tooltip> 405 + </TooltipProvider> 406 + {region} 407 + <StatusDot value={item.status} /> 408 + </TableCell> 409 + <TableCell className="text-right">{latency}</TableCell> 410 + </TableRow> 411 + ); 412 + }) 377 413 ) : ( 378 414 <TableRow> 379 415 <TableCell
+241 -2
apps/web/src/app/(pages)/(content)/play/checker/api/mock.ts
··· 1 - import type { RegionChecker } from "@/components/ping-response-analysis/utils"; 1 + import { 2 + type RegionChecker, 3 + cachedCheckerSchema, 4 + } from "@/components/ping-response-analysis/utils"; 2 5 import { wait } from "@/lib/utils"; 3 6 import type { Region } from "@openstatus/db/src/schema/constants"; 4 7 ··· 13 16 14 17 await wait(response.latency); 15 18 16 - return response as RegionChecker; 19 + return response as unknown as RegionChecker; 20 + } 21 + 22 + export async function mockCheckAllRegions() { 23 + const res = cachedCheckerSchema.safeParse(data); 24 + if (!res.success) { 25 + throw new Error(res.error.message); 26 + } 27 + return res.data; 17 28 } 18 29 19 30 export const data = { ··· 1379 1390 transferDone: 1724415476529, 1380 1391 }, 1381 1392 region: "yyz", 1393 + }, 1394 + { 1395 + type: "http", 1396 + state: "success", 1397 + status: 200, 1398 + latency: 720, 1399 + headers: { 1400 + Age: "0", 1401 + "Cache-Control": 1402 + "private, no-cache, no-store, max-age=0, must-revalidate", 1403 + "Content-Type": "text/html; charset=utf-8", 1404 + Date: "Fri, 23 Aug 2024 12:17:57 GMT", 1405 + Link: '</_next/static/media/162bf645eb375add-s.p.ttf>; rel=preload; as="font"; crossorigin=""; type="font/ttf", </_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"', 1406 + Server: "Koyeb", 1407 + "Set-Cookie": 1408 + "__Host-authjs.csrf-token=8f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b%7C4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f; Path=/; HttpOnly; Secure; SameSite=Lax", 1409 + "Strict-Transport-Security": "max-age=63072000", 1410 + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch", 1411 + "X-Matched-Path": "/", 1412 + "X-Powered-By": "Next.js", 1413 + "X-Koyeb-Cache": "MISS", 1414 + "X-Koyeb-Execution-Region": "fra", 1415 + "X-Koyeb-Id": "koyeb-fra::fra::abc123-1724415477000-def456789", 1416 + }, 1417 + timestamp: 1724415477000, 1418 + timing: { 1419 + dnsStart: 1724415477100, 1420 + dnsDone: 1724415477102, 1421 + connectStart: 1724415477102, 1422 + connectDone: 1724415477103, 1423 + tlsHandshakeStart: 1724415477103, 1424 + tlsHandshakeDone: 1724415477120, 1425 + firstByteStart: 1724415477120, 1426 + firstByteDone: 1724415477820, 1427 + transferStart: 1724415477820, 1428 + transferDone: 1724415477820, 1429 + }, 1430 + region: "koyeb_fra", 1431 + }, 1432 + { 1433 + type: "http", 1434 + state: "success", 1435 + status: 200, 1436 + latency: 680, 1437 + headers: { 1438 + Age: "0", 1439 + "Cache-Control": 1440 + "private, no-cache, no-store, max-age=0, must-revalidate", 1441 + "Content-Type": "text/html; charset=utf-8", 1442 + Date: "Fri, 23 Aug 2024 12:17:57 GMT", 1443 + Link: '</_next/static/media/162bf645eb375add-s.p.ttf>; rel=preload; as="font"; crossorigin=""; type="font/ttf", </_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"', 1444 + Server: "Koyeb", 1445 + "Set-Cookie": 1446 + "__Host-authjs.csrf-token=9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d%7C5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b; Path=/; HttpOnly; Secure; SameSite=Lax", 1447 + "Strict-Transport-Security": "max-age=63072000", 1448 + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch", 1449 + "X-Matched-Path": "/", 1450 + "X-Powered-By": "Next.js", 1451 + "X-Koyeb-Cache": "MISS", 1452 + "X-Koyeb-Execution-Region": "par", 1453 + "X-Koyeb-Id": "koyeb-par::par::xyz789-1724415477100-ghi012345", 1454 + }, 1455 + timestamp: 1724415477100, 1456 + timing: { 1457 + dnsStart: 1724415477200, 1458 + dnsDone: 1724415477202, 1459 + connectStart: 1724415477202, 1460 + connectDone: 1724415477203, 1461 + tlsHandshakeStart: 1724415477203, 1462 + tlsHandshakeDone: 1724415477220, 1463 + firstByteStart: 1724415477220, 1464 + firstByteDone: 1724415477900, 1465 + transferStart: 1724415477900, 1466 + transferDone: 1724415477900, 1467 + }, 1468 + region: "koyeb_par", 1469 + }, 1470 + { 1471 + type: "http", 1472 + state: "success", 1473 + status: 200, 1474 + latency: 450, 1475 + headers: { 1476 + Age: "0", 1477 + "Cache-Control": 1478 + "private, no-cache, no-store, max-age=0, must-revalidate", 1479 + "Content-Type": "text/html; charset=utf-8", 1480 + Date: "Fri, 23 Aug 2024 12:17:57 GMT", 1481 + Link: '</_next/static/media/162bf645eb375add-s.p.ttf>; rel=preload; as="font"; crossorigin=""; type="font/ttf", </_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"', 1482 + Server: "Koyeb", 1483 + "Set-Cookie": 1484 + "__Host-authjs.csrf-token=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e%7C6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c; Path=/; HttpOnly; Secure; SameSite=Lax", 1485 + "Strict-Transport-Security": "max-age=63072000", 1486 + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch", 1487 + "X-Matched-Path": "/", 1488 + "X-Powered-By": "Next.js", 1489 + "X-Koyeb-Cache": "MISS", 1490 + "X-Koyeb-Execution-Region": "sfo", 1491 + "X-Koyeb-Id": "koyeb-sfo::sfo::mno456-1724415477200-pqr789012", 1492 + }, 1493 + timestamp: 1724415477200, 1494 + timing: { 1495 + dnsStart: 1724415477300, 1496 + dnsDone: 1724415477302, 1497 + connectStart: 1724415477302, 1498 + connectDone: 1724415477303, 1499 + tlsHandshakeStart: 1724415477303, 1500 + tlsHandshakeDone: 1724415477320, 1501 + firstByteStart: 1724415477320, 1502 + firstByteDone: 1724415477650, 1503 + transferStart: 1724415477650, 1504 + transferDone: 1724415477650, 1505 + }, 1506 + region: "koyeb_sfo", 1507 + }, 1508 + { 1509 + type: "http", 1510 + state: "success", 1511 + status: 200, 1512 + latency: 920, 1513 + headers: { 1514 + Age: "0", 1515 + "Cache-Control": 1516 + "private, no-cache, no-store, max-age=0, must-revalidate", 1517 + "Content-Type": "text/html; charset=utf-8", 1518 + Date: "Fri, 23 Aug 2024 12:17:58 GMT", 1519 + Link: '</_next/static/media/162bf645eb375add-s.p.ttf>; rel=preload; as="font"; crossorigin=""; type="font/ttf", </_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"', 1520 + Server: "Koyeb", 1521 + "Set-Cookie": 1522 + "__Host-authjs.csrf-token=b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f%7C7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d; Path=/; HttpOnly; Secure; SameSite=Lax", 1523 + "Strict-Transport-Security": "max-age=63072000", 1524 + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch", 1525 + "X-Matched-Path": "/", 1526 + "X-Powered-By": "Next.js", 1527 + "X-Koyeb-Cache": "MISS", 1528 + "X-Koyeb-Execution-Region": "sin", 1529 + "X-Koyeb-Id": "koyeb-sin::sin::stu345-1724415477300-vwx678901", 1530 + }, 1531 + timestamp: 1724415477300, 1532 + timing: { 1533 + dnsStart: 1724415477400, 1534 + dnsDone: 1724415477402, 1535 + connectStart: 1724415477402, 1536 + connectDone: 1724415477403, 1537 + tlsHandshakeStart: 1724415477403, 1538 + tlsHandshakeDone: 1724415477420, 1539 + firstByteStart: 1724415477420, 1540 + firstByteDone: 1724415478340, 1541 + transferStart: 1724415478340, 1542 + transferDone: 1724415478340, 1543 + }, 1544 + region: "koyeb_sin", 1545 + }, 1546 + { 1547 + type: "http", 1548 + state: "success", 1549 + status: 200, 1550 + latency: 1100, 1551 + headers: { 1552 + Age: "0", 1553 + "Cache-Control": 1554 + "private, no-cache, no-store, max-age=0, must-revalidate", 1555 + "Content-Type": "text/html; charset=utf-8", 1556 + Date: "Fri, 23 Aug 2024 12:17:58 GMT", 1557 + Link: '</_next/static/media/162bf645eb375add-s.p.ttf>; rel=preload; as="font"; crossorigin=""; type="font/ttf", </_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"', 1558 + Server: "Koyeb", 1559 + "Set-Cookie": 1560 + "__Host-authjs.csrf-token=c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a%7C8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e; Path=/; HttpOnly; Secure; SameSite=Lax", 1561 + "Strict-Transport-Security": "max-age=63072000", 1562 + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch", 1563 + "X-Matched-Path": "/", 1564 + "X-Powered-By": "Next.js", 1565 + "X-Koyeb-Cache": "MISS", 1566 + "X-Koyeb-Execution-Region": "tyo", 1567 + "X-Koyeb-Id": "koyeb-tyo::tyo::yza234-1724415477400-bcd567890", 1568 + }, 1569 + timestamp: 1724415477400, 1570 + timing: { 1571 + dnsStart: 1724415477500, 1572 + dnsDone: 1724415477502, 1573 + connectStart: 1724415477502, 1574 + connectDone: 1724415477503, 1575 + tlsHandshakeStart: 1724415477503, 1576 + tlsHandshakeDone: 1724415477520, 1577 + firstByteStart: 1724415477520, 1578 + firstByteDone: 1724415478620, 1579 + transferStart: 1724415478620, 1580 + transferDone: 1724415478620, 1581 + }, 1582 + region: "koyeb_tyo", 1583 + }, 1584 + { 1585 + type: "http", 1586 + state: "success", 1587 + status: 200, 1588 + latency: 380, 1589 + headers: { 1590 + Age: "0", 1591 + "Cache-Control": 1592 + "private, no-cache, no-store, max-age=0, must-revalidate", 1593 + "Content-Type": "text/html; charset=utf-8", 1594 + Date: "Fri, 23 Aug 2024 12:17:58 GMT", 1595 + Link: '</_next/static/media/162bf645eb375add-s.p.ttf>; rel=preload; as="font"; crossorigin=""; type="font/ttf", </_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"', 1596 + Server: "Koyeb", 1597 + "Set-Cookie": 1598 + "__Host-authjs.csrf-token=d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b%7C9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f; Path=/; HttpOnly; Secure; SameSite=Lax", 1599 + "Strict-Transport-Security": "max-age=63072000", 1600 + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch", 1601 + "X-Matched-Path": "/", 1602 + "X-Powered-By": "Next.js", 1603 + "X-Koyeb-Cache": "MISS", 1604 + "X-Koyeb-Execution-Region": "was", 1605 + "X-Koyeb-Id": "koyeb-was::was::efg123-1724415477500-hij456789", 1606 + }, 1607 + timestamp: 1724415477500, 1608 + timing: { 1609 + dnsStart: 1724415477600, 1610 + dnsDone: 1724415477602, 1611 + connectStart: 1724415477602, 1612 + connectDone: 1724415477603, 1613 + tlsHandshakeStart: 1724415477603, 1614 + tlsHandshakeDone: 1724415477620, 1615 + firstByteStart: 1724415477620, 1616 + firstByteDone: 1724415478000, 1617 + transferStart: 1724415478000, 1618 + transferDone: 1724415478000, 1619 + }, 1620 + region: "koyeb_was", 1382 1621 }, 1383 1622 ], 1384 1623 };
+8 -5
apps/web/src/app/(pages)/(content)/play/checker/api/route.ts
··· 2 2 type Method, 3 3 checkRegion, 4 4 storeBaseCheckerData, 5 - storeCheckerData, 6 5 } from "@/components/ping-response-analysis/utils"; 7 6 import { iteratorToStream, yieldMany } from "@/lib/stream"; 8 7 import { wait } from "@/lib/utils"; 9 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 8 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 10 9 import { mockCheckRegion } from "./mock"; 11 10 12 11 export const runtime = "edge"; ··· 17 16 url, 18 17 method, 19 18 id, 20 - }: { url: string; method: Method; id: string }) { 19 + }: { 20 + url: string; 21 + method: Method; 22 + id: string; 23 + }) { 21 24 // Create an array to store all the promises 22 - const promises = flyRegions.map(async (region, index) => { 25 + const promises = monitorRegions.map(async (region, index) => { 23 26 try { 24 27 // Perform the fetch operation 25 28 const check = ··· 32 35 } 33 36 34 37 if (check.state === "success") { 35 - storeCheckerData({ check, id }); 38 + // storeCheckerData({ check, id }); 36 39 } 37 40 38 41 return encoder.encode(
+6 -1
apps/web/src/app/(pages)/(content)/play/checker/page.tsx
··· 11 11 import CheckerPlay from "./_components/checker-play"; 12 12 import { GlobalMonitoring } from "./_components/global-monitoring"; 13 13 import { Testimonial } from "./_components/testimonial"; 14 + import { mockCheckAllRegions } from "./api/mock"; 14 15 import { searchParamsCache } from "./search-params"; 15 16 16 17 const title = "Global Speed Checker"; ··· 41 42 const searchParams = await props.searchParams; 42 43 const { id } = searchParamsCache.parse(searchParams); 43 44 44 - const data = id ? await getCheckerDataById(id) : null; 45 + const data = id 46 + ? process.env.NODE_ENV === "development" 47 + ? await mockCheckAllRegions() 48 + : await getCheckerDataById(id) 49 + : null; 45 50 46 51 if (id && !data) return redirect("/play/checker"); 47 52
+2 -2
apps/web/src/app/(pages)/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/_components/data-table-wrapper.tsx
··· 18 18 import { ResponseDetailTabs } from "@/components/ping-response-analysis/response-detail-tabs"; 19 19 import type { Trigger } from "@/lib/monitor/utils"; 20 20 import { api } from "@/trpc/rq-client"; 21 - import type { monitorFlyRegionSchema } from "@openstatus/db/src/schema/constants"; 21 + import type { monitorRegionSchema } from "@openstatus/db/src/schema/constants"; 22 22 import type { z } from "zod"; 23 23 24 24 // FIXME: use proper type ··· 26 26 type: "http" | "tcp"; 27 27 monitorId: string; 28 28 latency: number; 29 - region: z.infer<typeof monitorFlyRegionSchema>; 29 + region: z.infer<typeof monitorRegionSchema>; 30 30 statusCode?: number | null; 31 31 timestamp: number; 32 32 workspaceId: string;
+2 -2
apps/web/src/app/(pages)/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/search-params.ts
··· 1 1 import { periods, triggers } from "@/lib/monitor/utils"; 2 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 2 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 3 import { 4 4 createSearchParamsCache, 5 5 parseAsArrayOf, ··· 15 15 cronTimestamp: parseAsInteger, 16 16 error: parseAsArrayOf(parseAsBoolean), 17 17 period: parseAsStringLiteral(periods).withDefault(DEFAULT_PERIOD), 18 - regions: parseAsArrayOf(parseAsStringLiteral(flyRegions)), 18 + regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)), 19 19 pageSize: parseAsInteger.withDefault(10), 20 20 pageIndex: parseAsInteger.withDefault(0), 21 21 trigger: parseAsArrayOf(parseAsStringLiteral(triggers)),
+2 -2
apps/web/src/app/(pages)/app/[workspaceSlug]/(dashboard)/monitors/[id]/details/search-params.ts
··· 1 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 1 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 2 2 import { 3 3 createSearchParamsCache, 4 4 parseAsInteger, ··· 9 9 export const searchParamsParsers = { 10 10 monitorId: parseAsString.withDefault(""), 11 11 url: parseAsString.withDefault(""), 12 - region: parseAsStringLiteral(flyRegions).withDefault("ams"), 12 + region: parseAsStringLiteral(monitorRegions).withDefault("ams"), 13 13 cronTimestamp: parseAsInteger.withDefault(0), 14 14 }; 15 15 export const searchParamsCache = createSearchParamsCache(searchParamsParsers);
+5 -2
apps/web/src/app/(pages)/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx
··· 1 1 import { notFound } from "next/navigation"; 2 2 3 - import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; 3 + import { 4 + type Region, 5 + monitorRegions, 6 + } from "@openstatus/db/src/schema/constants"; 4 7 import { Separator } from "@openstatus/ui"; 5 8 6 9 import { CombinedChartWrapper } from "@/components/monitor-charts/combined-chart-wrapper"; ··· 68 71 period !== DEFAULT_PERIOD || 69 72 quantile !== DEFAULT_QUANTILE || 70 73 interval !== DEFAULT_INTERVAL || 71 - flyRegions.length !== regions.length; 74 + monitorRegions.length !== regions.length; 72 75 73 76 // GET VALUES FOR BLOG POST 74 77 // console.log(
+3 -3
apps/web/src/app/(pages)/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/search-params.ts
··· 1 1 import { intervals, periods, quantiles } from "@/lib/monitor/utils"; 2 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 2 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 3 import { 4 4 createSearchParamsCache, 5 5 parseAsArrayOf, ··· 17 17 quantile: parseAsStringLiteral(quantiles).withDefault(DEFAULT_QUANTILE), 18 18 interval: parseAsStringLiteral(intervals).withDefault(DEFAULT_INTERVAL), 19 19 period: parseAsStringLiteral(periods).withDefault(DEFAULT_PERIOD), 20 - regions: parseAsArrayOf(parseAsStringLiteral(flyRegions)).withDefault([ 21 - ...flyRegions, 20 + regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)).withDefault([ 21 + ...monitorRegions, 22 22 ]), 23 23 }; 24 24 export const searchParamsCache = createSearchParamsCache(searchParamsParsers);
+5 -2
apps/web/src/app/(pages)/public/monitors/[id]/page.tsx
··· 1 1 import { notFound } from "next/navigation"; 2 2 3 - import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; 3 + import { 4 + type Region, 5 + monitorRegions, 6 + } from "@openstatus/db/src/schema/constants"; 4 7 import { Separator } from "@openstatus/ui"; 5 8 6 9 import { Shell } from "@/components/dashboard/shell"; ··· 78 81 period !== DEFAULT_PERIOD || 79 82 quantile !== DEFAULT_QUANTILE || 80 83 interval !== DEFAULT_INTERVAL || 81 - flyRegions.length !== regions.length; 84 + monitorRegions.length !== regions.length; 82 85 83 86 return ( 84 87 <div className="relative flex w-full flex-col gap-6">
+3 -3
apps/web/src/app/(pages)/public/monitors/[id]/search-params.ts
··· 1 1 import { intervals, quantiles } from "@/lib/monitor/utils"; 2 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 2 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 3 import { 4 4 createSearchParamsCache, 5 5 parseAsArrayOf, ··· 19 19 quantile: parseAsStringLiteral(quantiles).withDefault(DEFAULT_QUANTILE), 20 20 interval: parseAsStringLiteral(intervals).withDefault(DEFAULT_INTERVAL), 21 21 period: parseAsStringLiteral(periods).withDefault(DEFAULT_PERIOD), 22 - regions: parseAsArrayOf(parseAsStringLiteral(flyRegions)).withDefault([ 23 - ...flyRegions, 22 + regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)).withDefault([ 23 + ...monitorRegions, 24 24 ]), 25 25 }; 26 26 export const searchParamsCache = createSearchParamsCache(searchParamsParsers);
+27 -12
apps/web/src/app/api/checker/cron/_cron.ts
··· 15 15 } from "@openstatus/db/src/schema"; 16 16 17 17 import { env } from "@/env"; 18 + import type { Region } from "@openstatus/db/src/schema/constants"; 18 19 import { 19 20 type httpPayloadSchema, 20 21 type tpcPayloadSchema, 21 22 transformHeaders, 22 23 } from "@openstatus/utils"; 24 + import { regionDict } from "@openstatus/utils"; 23 25 24 26 const periodicityAvailable = selectMonitorSchema.pick({ periodicity: true }); 25 27 ··· 92 94 } 93 95 94 96 for (const row of monitors.data) { 95 - const selectedRegions = row.regions.length > 0 ? row.regions : ["ams"]; 97 + // const selectedRegions = row.regions.length > 0 ? row.regions : ["ams"]; 96 98 97 99 const result = await db 98 100 .select() ··· 107 109 continue; 108 110 } 109 111 110 - for (const region of selectedRegions) { 112 + for (const region of row.regions) { 111 113 const status = 112 114 monitorStatus.data.find((m) => region === m.region)?.status || "active"; 113 115 const response = createCronTask({ ··· 158 160 client: CloudTasksClient; 159 161 parent: string; 160 162 status: MonitorStatus; 161 - region: string; 163 + region: Region; 162 164 }) => { 163 165 let payload: 164 166 | z.infer<typeof httpPayloadSchema> ··· 214 216 if (!payload) { 215 217 throw new Error("Invalid jobType"); 216 218 } 217 - 219 + const regionInfo = regionDict[region]; 220 + let regionHeader = {}; 221 + if (regionInfo.provider === "fly") { 222 + regionHeader = { "fly-prefer-region": region }; 223 + } 224 + if (regionInfo.provider === "koyeb") { 225 + regionHeader = { "X-KOYEB-REGION-OVERRIDE": region.replace("koyeb_", "") }; 226 + } 218 227 const newTask: google.cloud.tasks.v2beta3.ITask = { 219 228 httpRequest: { 220 229 headers: { 221 230 "Content-Type": "application/json", // Set content type to ensure compatibility your application's request parsing 222 - "fly-prefer-region": region, // Specify the region you want the request to be sent to 231 + ...regionHeader, 223 232 Authorization: `Basic ${env.CRON_SECRET}`, 224 233 }, 225 234 httpMethod: "POST", 226 - url: generateUrl({ row }), 235 + url: generateUrl({ row, region }), 227 236 body: Buffer.from(JSON.stringify(payload)).toString("base64"), 228 237 }, 229 238 scheduleTime: { ··· 235 244 return client.createTask(request); 236 245 }; 237 246 238 - function generateUrl({ row }: { row: z.infer<typeof selectMonitorSchema> }) { 239 - switch (row.jobType) { 240 - case "http": 241 - return `https://openstatus-checker.fly.dev/checker/http?monitor_id=${row.id}`; 242 - case "tcp": 243 - return `https://openstatus-checker.fly.dev/checker/tcp?monitor_id=${row.id}`; 247 + function generateUrl({ 248 + row, 249 + region, 250 + }: { row: z.infer<typeof selectMonitorSchema>; region: Region }) { 251 + const regionInfo = regionDict[region]; 252 + 253 + switch (regionInfo.provider) { 254 + case "fly": 255 + return `https://openstatus-checker.fly.dev/checker/${row.jobType}?monitor_id=${row.id}`; 256 + case "koyeb": 257 + return `openstatus-checker.koyeb.app/checker/${row.jobType}?monitor_id=${row.id}`; 258 + 244 259 default: 245 260 throw new Error("Invalid jobType"); 246 261 }
+2 -2
apps/web/src/app/api/checker/test/http/route.ts
··· 1 1 import { NextResponse } from "next/server"; 2 2 import { z } from "zod"; 3 3 4 - import { monitorFlyRegionSchema } from "@openstatus/db/src/schema/constants"; 4 + import { monitorRegionSchema } from "@openstatus/db/src/schema/constants"; 5 5 6 6 import { checkRegion } from "@/components/ping-response-analysis/utils"; 7 7 import { httpPayloadSchema } from "@openstatus/utils"; ··· 21 21 const json = await request.json(); 22 22 const _valid = httpPayloadSchema 23 23 .pick({ url: true, method: true, headers: true, body: true }) 24 - .merge(z.object({ region: monitorFlyRegionSchema.default("ams") })) 24 + .merge(z.object({ region: monitorRegionSchema.default("ams") })) 25 25 .safeParse(json); 26 26 27 27 if (!_valid.success) {
+4 -4
apps/web/src/app/api/checker/test/tcp/route.ts
··· 2 2 import { z } from "zod"; 3 3 4 4 import { 5 - type MonitorFlyRegion, 6 - monitorFlyRegionSchema, 5 + type Region, 6 + monitorRegionSchema, 7 7 } from "@openstatus/db/src/schema/constants"; 8 8 9 9 import { TCPResponse, tcpPayload } from "./schema"; ··· 22 22 const json = await request.json(); 23 23 const _valid = tcpPayload 24 24 .pick({ url: true }) 25 - .merge(z.object({ region: monitorFlyRegionSchema.default("ams") })) 25 + .merge(z.object({ region: monitorRegionSchema.default("ams") })) 26 26 .safeParse(json); 27 27 28 28 if (!_valid.success) { ··· 39 39 return NextResponse.json({ success: false }, { status: 400 }); 40 40 } 41 41 } 42 - async function checkTCP(url: string, region: MonitorFlyRegion) { 42 + async function checkTCP(url: string, region: Region) { 43 43 // 44 44 const res = await fetch(`https://checker.openstatus.dev/tcp/${region}`, { 45 45 headers: {
+2 -2
apps/web/src/app/api/checker/test/tcp/schema.ts
··· 1 1 import { z } from "zod"; 2 2 3 - import { monitorFlyRegionSchema } from "@openstatus/db/src/schema/constants"; 3 + import { monitorRegionSchema } from "@openstatus/db/src/schema/constants"; 4 4 5 5 export const tcpPayload = z.object({ 6 6 workspaceId: z.string(), ··· 22 22 tcpDone: z.number(), 23 23 }), 24 24 error: z.string().optional(), 25 - region: monitorFlyRegionSchema, 25 + region: monitorRegionSchema, 26 26 latency: z.number().optional(), 27 27 }); 28 28
+5 -2
apps/web/src/app/status-page/[domain]/monitors/[id]/page.tsx
··· 1 1 import { notFound } from "next/navigation"; 2 2 3 - import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; 3 + import { 4 + type Region, 5 + monitorRegions, 6 + } from "@openstatus/db/src/schema/constants"; 4 7 import { Separator } from "@openstatus/ui/src/components/separator"; 5 8 6 9 import { Header } from "@/components/dashboard/header"; ··· 99 102 period !== DEFAULT_PERIOD || 100 103 quantile !== DEFAULT_QUANTILE || 101 104 interval !== DEFAULT_INTERVAL || 102 - flyRegions.length !== regions.length; 105 + monitorRegions.length !== regions.length; 103 106 104 107 return ( 105 108 <div className="relative flex w-full flex-col gap-6">
+3 -3
apps/web/src/app/status-page/[domain]/monitors/[id]/search-params.ts
··· 1 1 import { intervals, quantiles } from "@/lib/monitor/utils"; 2 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 2 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 3 3 import { 4 4 createSearchParamsCache, 5 5 parseAsArrayOf, ··· 19 19 quantile: parseAsStringLiteral(quantiles).withDefault(DEFAULT_QUANTILE), 20 20 interval: parseAsStringLiteral(intervals).withDefault(DEFAULT_INTERVAL), 21 21 period: parseAsStringLiteral(periods).withDefault(DEFAULT_PERIOD), 22 - regions: parseAsArrayOf(parseAsStringLiteral(flyRegions)).withDefault([ 23 - ...flyRegions, 22 + regions: parseAsArrayOf(parseAsStringLiteral(monitorRegions)).withDefault([ 23 + ...monitorRegions, 24 24 ]), 25 25 }; 26 26 export const searchParamsCache = createSearchParamsCache(searchParamsParsers);
+4 -4
apps/web/src/components/data-table/columns.tsx
··· 4 4 import { format } from "date-fns"; 5 5 import type * as z from "zod"; 6 6 7 - import { flyRegionsDict } from "@openstatus/utils"; 7 + import { regionDict } from "@openstatus/utils"; 8 8 9 9 import type { Trigger } from "@/lib/monitor/utils"; 10 - import type { monitorFlyRegionSchema } from "@openstatus/db/src/schema/constants"; 10 + import type { monitorRegionSchema } from "@openstatus/db/src/schema/constants"; 11 11 import { TriggerIconWithTooltip } from "../monitor/trigger-icon-with-tooltip"; 12 12 import { DataTableColumnHeader } from "./data-table-column-header"; 13 13 import { DataTableStatusBadge } from "./data-table-status-badge"; ··· 16 16 type: "http" | "tcp"; 17 17 monitorId: string; 18 18 latency: number; 19 - region: z.infer<typeof monitorFlyRegionSchema>; 19 + region: z.infer<typeof monitorRegionSchema>; 20 20 statusCode?: number | null; 21 21 timestamp: number; 22 22 workspaceId: string; ··· 95 95 <div> 96 96 <span className="font-mono">{String(row.getValue("region"))} </span> 97 97 <span className="text-muted-foreground text-xs"> 98 - {flyRegionsDict[row.original.region]?.location} 98 + {regionDict[row.original.region]?.location} 99 99 </span> 100 100 </div> 101 101 );
+5 -5
apps/web/src/components/data-table/data-table-toolbar.tsx
··· 5 5 import { useRouter, useSearchParams } from "next/navigation"; 6 6 7 7 import { Button } from "@openstatus/ui/src/components/button"; 8 - import { flyRegionsDict } from "@openstatus/utils"; 8 + import { regionDict } from "@openstatus/utils"; 9 9 10 10 import { Icons } from "@/components/icons"; 11 11 import { codesDict } from "@/data/code-dictionary"; ··· 44 44 <DataTableFacetedFilter 45 45 column={table.getColumn("region")} 46 46 title="Region" 47 - options={Object.keys(flyRegionsDict).map((key) => { 48 - const typedKey = key as keyof typeof flyRegionsDict; 47 + options={Object.keys(regionDict).map((key) => { 48 + const typedKey = key as keyof typeof regionDict; 49 49 return { 50 - label: flyRegionsDict[typedKey].location, 51 - value: flyRegionsDict[typedKey].code, 50 + label: regionDict[typedKey].location, 51 + value: regionDict[typedKey].code, 52 52 }; 53 53 })} 54 54 />
+2 -2
apps/web/src/components/data-table/single-region/columns.tsx
··· 4 4 import { formatNumber } from "@/components/monitor-dashboard/metrics-card"; 5 5 import type { ResponseTimeMetricsByRegion } from "@/lib/tb"; 6 6 import type { Region } from "@openstatus/db/src/schema/constants"; 7 - import { flyRegionsDict } from "@openstatus/utils"; 7 + import { regionDict } from "@openstatus/utils"; 8 8 import type { ColumnDef } from "@tanstack/react-table"; 9 9 import { DataTableColumnHeader } from "./data-table-column-header"; 10 10 ··· 20 20 header: "Region", 21 21 cell: ({ row }) => { 22 22 const region = row.getValue("region") as Region; 23 - const { code, flag, location } = flyRegionsDict[region]; 23 + const { code, flag, location } = regionDict[region]; 24 24 return ( 25 25 <div> 26 26 <p className="text-muted-foreground text-xs">{location}</p>
+2 -2
apps/web/src/components/forms/monitor/form.tsx
··· 28 28 import { toast, toastAction } from "@/lib/toast"; 29 29 import { formatDuration } from "@/lib/utils"; 30 30 import { api } from "@/trpc/client"; 31 - import type { MonitorFlyRegion } from "@openstatus/db/src/schema/constants"; 31 + import type { Region } from "@openstatus/db/src/schema/constants"; 32 32 import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 33 33 import { SaveButton } from "../shared/save-button"; 34 34 import { General } from "./general"; ··· 175 175 } 176 176 }; 177 177 178 - const pingEndpoint = async (region?: MonitorFlyRegion) => { 178 + const pingEndpoint = async (region?: Region) => { 179 179 try { 180 180 const { 181 181 url,
+8 -8
apps/web/src/components/forms/monitor/request-test-button.tsx
··· 7 7 import { deserialize } from "@openstatus/assertions"; 8 8 import type { InsertMonitor } from "@openstatus/db/src/schema"; 9 9 import { 10 - type MonitorFlyRegion, 11 - flyRegions, 10 + type Region, 11 + monitorRegions, 12 12 } from "@openstatus/db/src/schema/constants"; 13 13 import { 14 14 Button, ··· 26 26 TooltipProvider, 27 27 TooltipTrigger, 28 28 } from "@openstatus/ui"; 29 - import { flyRegionsDict } from "@openstatus/utils"; 29 + import { regionDict } from "@openstatus/utils"; 30 30 31 31 import { LoadingAnimation } from "@/components/loading-animation"; 32 32 import { RegionInfo } from "@/components/ping-response-analysis/region-info"; ··· 40 40 form: UseFormReturn<InsertMonitor>; 41 41 limits: Limits; 42 42 pingEndpoint( 43 - region?: MonitorFlyRegion, 43 + region?: Region, 44 44 ): Promise<{ data?: RegionChecker; error?: string }>; 45 45 } 46 46 ··· 48 48 const [check, setCheck] = React.useState< 49 49 { data: RegionChecker; error?: string } | undefined 50 50 >(); 51 - const [value, setValue] = React.useState<MonitorFlyRegion>(flyRegions[0]); 51 + const [value, setValue] = React.useState<Region>(monitorRegions[0]); 52 52 const [isPending, startTransition] = React.useTransition(); 53 53 54 54 const onClick = () => { ··· 77 77 }); 78 78 }; 79 79 80 - const { flag } = flyRegionsDict[value as keyof typeof flyRegionsDict]; 80 + const { flag } = regionDict[value as keyof typeof regionDict]; 81 81 82 82 const { statusAssertions, headerAssertions } = form.getValues(); 83 83 ··· 88 88 <div className="group flex h-10 items-center rounded-md bg-transparent text-sm ring-offset-background focus-within:outline-hidden focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"> 89 89 <Select 90 90 value={value} 91 - onValueChange={(value: MonitorFlyRegion) => setValue(value)} 91 + onValueChange={(value: Region) => setValue(value)} 92 92 > 93 93 <SelectTrigger 94 94 className="flex-1 rounded-r-none border-accent focus:ring-0" ··· 98 98 </SelectTrigger> 99 99 <SelectContent> 100 100 {regions.map((region) => { 101 - const { flag } = flyRegionsDict[region]; 101 + const { flag } = regionDict[region]; 102 102 return ( 103 103 <SelectItem key={region} value={region}> 104 104 {flag} <span className="ml-1 font-mono">{region}</span>
+8 -9
apps/web/src/components/forms/monitor/select-region.tsx
··· 17 17 PopoverContent, 18 18 PopoverTrigger, 19 19 } from "@openstatus/ui/src/components/popover"; 20 - import { 21 - type Continent, 22 - type RegionInfo, 23 - flyRegionsDict, 24 - } from "@openstatus/utils"; 20 + import { type Continent, type RegionInfo, regionDict } from "@openstatus/utils"; 25 21 26 22 import { cn } from "@/lib/utils"; 27 - import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; 23 + import { 24 + type Region, 25 + monitorRegions, 26 + } from "@openstatus/db/src/schema/constants"; 28 27 29 28 interface SelectRegionProps extends Omit<ButtonProps, "onChange"> { 30 29 allowedRegions: Region[]; ··· 39 38 className, 40 39 ...props 41 40 }: SelectRegionProps) { 42 - const regionsByContinent = flyRegions.reduce( 41 + const regionsByContinent = monitorRegions.reduce( 43 42 (prev, curr) => { 44 - const region = flyRegionsDict[curr]; 43 + const region = regionDict[curr]; 45 44 46 45 const item = prev.find((r) => r.continent === region.continent); 47 46 ··· 119 118 </div> 120 119 <div className="flex w-full justify-between"> 121 120 <span> 122 - {code}{" "} 121 + {code.replace(/(koyeb_|railway_|fly_)/g, "")}{" "} 123 122 <span className="truncate text-muted-foreground"> 124 123 {location} 125 124 </span>
+3 -3
apps/web/src/components/marketing/feature/index.tsx
··· 9 9 import { StatusReport } from "@/components/status-page/status-report"; 10 10 import { Tracker } from "@/components/tracker/tracker"; 11 11 import type { Region } from "@openstatus/db/src/schema/constants"; 12 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 12 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 13 13 import { Button, InputWithAddons } from "@openstatus/ui"; 14 14 import { Skeleton } from "@openstatus/ui/src/components/skeleton"; 15 15 import { allUnrelateds } from "content-collections"; ··· 286 286 <Suspense fallback={<Skeleton />}> 287 287 <div className="m-auto"> 288 288 <RegionsPreset 289 - regions={flyRegions as unknown as Region[]} 290 - selectedRegions={flyRegions as unknown as Region[]} 289 + regions={monitorRegions as unknown as Region[]} 290 + selectedRegions={monitorRegions as unknown as Region[]} 291 291 /> 292 292 </div> 293 293 </Suspense>
+6
apps/web/src/components/monitor-charts/chart.tsx
··· 144 144 waw: { label: "WAW", color: "hsl(0 0% 45.1%)" }, 145 145 yul: { label: "YUL", color: "hsl(25 5.3% 44.7%)" }, 146 146 yyz: { label: "YYZ", color: "hsl(0 84.2% 60.2%)" }, 147 + koyeb_fra: { label: "FRA (koyeb)", color: "hsl(0 0% 45.1%)" }, 148 + koyeb_par: { label: "PAR (koyeb)", color: "hsl(0 0% 45.1%)" }, 149 + koyeb_sfo: { label: "SFO (koyeb)", color: "hsl(0 0% 45.1%)" }, 150 + koyeb_sin: { label: "SIN (koyeb)", color: "hsl(0 0% 45.1%)" }, 151 + koyeb_tyo: { label: "TYO (koyeb)", color: "hsl(0 0% 45.1%)" }, 152 + koyeb_was: { label: "WAS (koyeb)", color: "hsl(0 0% 45.1%)" }, 147 153 };
+2 -2
apps/web/src/components/monitor-charts/region-table.tsx
··· 7 7 TableHeader, 8 8 TableRow, 9 9 } from "@openstatus/ui/src/components/table"; 10 - import { flyRegionsDict } from "@openstatus/utils"; 10 + import { regionDict } from "@openstatus/utils"; 11 11 12 12 import { formatNumber } from "@/components/monitor-dashboard/metrics-card"; 13 13 import type { ResponseTimeMetricsByRegion } from "@/lib/tb"; ··· 49 49 {regions 50 50 .filter((region) => regions.includes(region)) 51 51 .map((region) => { 52 - const { code, flag, location } = flyRegionsDict[region]; 52 + const { code, flag, location } = regionDict[region]; 53 53 const metrics = metricsByRegion.find((m) => m.region === region); 54 54 return ( 55 55 <TableRow key={region}>
+3 -3
apps/web/src/components/monitor-charts/utils.tsx
··· 1 - import { flyRegionsDict } from "@openstatus/utils"; 1 + import { regionDict } from "@openstatus/utils"; 2 2 3 3 import type { Period, Quantile } from "@/lib/monitor/utils"; 4 4 import type { ResponseGraph } from "@/lib/tb"; ··· 24 24 (acc, curr) => { 25 25 const { timestamp, region } = curr; 26 26 const latency = curr[`${quantile}Latency`]; 27 - const { flag, code, location } = flyRegionsDict[region]; 27 + const { flag, code, location } = regionDict[region]; 28 28 const fullNameRegion = `${code}`; 29 29 regions[fullNameRegion] = { flag, code, location }; // to get the region keys 30 30 if (timestamp === currentTimestamp) { ··· 73 73 } 74 74 75 75 export function regionFormatter(region: Region) { 76 - const { code, flag } = flyRegionsDict[region]; 76 + const { code, flag } = regionDict[region]; 77 77 return `${flag} ${code}`; 78 78 }
+26 -12
apps/web/src/components/monitor-dashboard/region-preset.tsx
··· 1 1 "use client"; 2 2 3 - import { Check, ChevronsUpDown, Globe2 } from "lucide-react"; 3 + import { Check, ChevronsUpDown, Globe, Globe2 } from "lucide-react"; 4 4 5 5 import { Button, type ButtonProps } from "@openstatus/ui/src/components/button"; 6 6 import { ··· 17 17 PopoverContent, 18 18 PopoverTrigger, 19 19 } from "@openstatus/ui/src/components/popover"; 20 - import { 21 - type Continent, 22 - type RegionInfo, 23 - flyRegionsDict, 24 - } from "@openstatus/utils"; 20 + import { type Continent, type RegionInfo, regionDict } from "@openstatus/utils"; 25 21 26 22 import { cn } from "@/lib/utils"; 27 - import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; 23 + import { 24 + type Region, 25 + monitorRegions, 26 + } from "@openstatus/db/src/schema/constants"; 27 + import { Fly, Koyeb, Railway } from "@openstatus/icons"; 28 28 import { parseAsArrayOf, parseAsStringLiteral, useQueryState } from "nuqs"; 29 29 30 30 interface RegionsPresetProps extends ButtonProps { ··· 41 41 // TODO: check with the RSC pages 42 42 const [selected, setSelected] = useQueryState( 43 43 "regions", 44 - parseAsArrayOf(parseAsStringLiteral(flyRegions)) 44 + parseAsArrayOf(parseAsStringLiteral(monitorRegions)) 45 45 .withDefault(selectedRegions.filter((r) => regions?.includes(r))) 46 46 .withOptions({ 47 47 shallow: false, // required for SSR to call the RSC ··· 53 53 const regionsByContinent = regions 54 54 .reduce( 55 55 (prev, curr) => { 56 - const region = flyRegionsDict[curr]; 56 + const region = regionDict[curr]; 57 57 58 58 const item = prev.find((r) => r.continent === region.continent); 59 59 ··· 88 88 <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> 89 89 </Button> 90 90 </PopoverTrigger> 91 - <PopoverContent className="p-0" align="start"> 91 + <PopoverContent className="w-80 p-0" align="start"> 92 92 <Command 93 93 // FIXME: keywords not taken - it would be great to search for "Europe" 94 94 // filter={(value, search, keywords) => { ··· 137 137 > 138 138 <Check className={cn("h-4 w-4")} /> 139 139 </div> 140 - <div className="flex w-full justify-between"> 140 + <div className="flex w-full items-center gap-1"> 141 141 <span> 142 - {code}{" "} 142 + {(() => { 143 + switch (region.provider) { 144 + case "fly": 145 + return <Fly className="size-4" />; 146 + case "railway": 147 + return <Railway className="size-4" />; 148 + case "koyeb": 149 + return <Koyeb className="size-4" />; 150 + default: 151 + return <Globe className="size-4" />; 152 + } 153 + })()} 154 + </span> 155 + <span> 156 + {code.replace(/(koyeb_|railway_|fly_)/g, "")}{" "} 143 157 <span className="truncate text-muted-foreground"> 144 158 {location} 145 159 </span>
+38 -4
apps/web/src/components/ping-response-analysis/columns.tsx
··· 3 3 import type { ColumnDef } from "@tanstack/react-table"; 4 4 import { type RegionChecker, latencyFormatter, regionFormatter } from "./utils"; 5 5 6 - import { flyRegionsDict } from "@openstatus/utils"; 6 + import { Fly, Koyeb, Railway } from "@openstatus/icons"; 7 + import { 8 + Tooltip, 9 + TooltipContent, 10 + TooltipProvider, 11 + TooltipTrigger, 12 + } from "@openstatus/ui"; 13 + import { regionDict } from "@openstatus/utils"; 14 + import { Globe } from "lucide-react"; 7 15 import { DataTableColumnHeader } from "../data-table/data-table-column-header"; 8 16 import { StatusCodeBadge } from "../monitor/status-code-badge"; 9 17 ··· 13 21 accessorFn: (row) => row.region, 14 22 header: "Key", 15 23 cell: ({ row }) => { 16 - return <div className="font-mono">{row.original.region}</div>; 24 + return ( 25 + <div className="font-mono"> 26 + {row.original.region.replace(/(koyeb_|railway_|fly_)/g, "")} 27 + </div> 28 + ); 17 29 }, 18 30 enableHiding: false, 19 31 }, ··· 22 34 accessorFn: (row) => row.region, 23 35 header: "Region", 24 36 cell: ({ row }) => { 37 + const region = regionDict[row.original.region]; 25 38 return ( 26 - <div className="text-muted-foreground"> 39 + <div className="flex items-center gap-1 text-muted-foreground"> 40 + <TooltipProvider> 41 + <Tooltip delayDuration={0}> 42 + <TooltipTrigger type="button"> 43 + {(() => { 44 + switch (region.provider) { 45 + case "fly": 46 + return <Fly className="size-4" />; 47 + case "railway": 48 + return <Railway className="size-4" />; 49 + case "koyeb": 50 + return <Koyeb className="size-4" />; 51 + default: 52 + return <Globe className="size-4" />; 53 + } 54 + })()} 55 + </TooltipTrigger> 56 + <TooltipContent className="capitalize"> 57 + {region.provider} 58 + </TooltipContent> 59 + </Tooltip> 60 + </TooltipProvider> 27 61 {regionFormatter(row.original.region, "long")} 28 62 </div> 29 63 ); ··· 31 65 filterFn: (row, _id, filterValue) => { 32 66 const region = regionFormatter(row.original.region, "long").toLowerCase(); 33 67 const continent = 34 - flyRegionsDict[row.original.region].continent.toLocaleLowerCase(); 68 + regionDict[row.original.region].continent.toLocaleLowerCase(); 35 69 return `${region} ${continent}`.includes(filterValue.toLowerCase()); 36 70 }, 37 71 },
+1 -1
apps/web/src/components/ping-response-analysis/multi-region-chart.tsx
··· 77 77 transform="rotate(-35)" 78 78 className="font-mono" 79 79 > 80 - {payload.value} 80 + {payload.value.replace(/(koyeb_|railway_|fly_)/g, "")} 81 81 </text> 82 82 </g> 83 83 );
+33 -15
apps/web/src/components/ping-response-analysis/utils.ts
··· 3 3 4 4 import { 5 5 flyRegions, 6 - monitorFlyRegionSchema, 6 + monitorRegionSchema, 7 7 } from "@openstatus/db/src/schema/constants"; 8 - import type { MonitorFlyRegion } from "@openstatus/db/src/schema/constants"; 9 - import { continentDict, flyRegionsDict } from "@openstatus/utils"; 8 + import type { Region } from "@openstatus/db/src/schema/constants"; 9 + import { continentDict, regionDict } from "@openstatus/utils"; 10 10 11 11 export function latencyFormatter(value: number) { 12 12 return `${new Intl.NumberFormat("us").format(value).toString()}ms`; ··· 16 16 return new Date(timestamp).toUTCString(); // GMT format 17 17 } 18 18 19 - export function continentFormatter(region: MonitorFlyRegion) { 20 - const continent = flyRegionsDict[region].continent; 19 + export function continentFormatter(region: Region) { 20 + const continent = regionDict[region].continent; 21 21 return continentDict[continent].code; 22 22 } 23 23 24 24 export function regionFormatter( 25 - region: MonitorFlyRegion, 25 + region: Region, 26 26 type: "short" | "long" = "short", 27 27 ) { 28 - const { code, flag, location } = flyRegionsDict[region]; 28 + const { code, flag, location } = regionDict[region]; 29 29 if (type === "short") return `${code} ${flag}`; 30 30 return `${location} ${flag}`; 31 31 } ··· 114 114 url: z.string(), 115 115 timestamp: z.number(), 116 116 method: z.enum(["GET", "POST", "PUT", "DELETE"]).default("GET"), 117 - checks: checkerSchema.extend({ region: monitorFlyRegionSchema }).array(), 117 + checks: checkerSchema.extend({ region: monitorRegionSchema }).array(), 118 118 }); 119 119 120 120 const errorRequest = z.object({ ··· 123 123 }); 124 124 125 125 export const regionCheckerSchema = checkerSchema.extend({ 126 - region: monitorFlyRegionSchema, 126 + region: monitorRegionSchema, 127 127 state: z.literal("success").default("success"), 128 128 }); 129 129 130 130 export const regionCheckerSchemaResponse = regionCheckerSchema.or( 131 131 errorRequest.extend({ 132 - region: monitorFlyRegionSchema, 132 + region: monitorRegionSchema, 133 133 }), 134 134 ); 135 135 export type Timing = z.infer<typeof timingSchema>; ··· 152 152 export type ErrorRequest = z.infer<typeof errorRequest>; 153 153 export async function checkRegion( 154 154 url: string, 155 - region: MonitorFlyRegion, 155 + region: Region, 156 156 opts?: { 157 157 method?: Method; 158 158 headers?: { value: string; key: string }[]; ··· 160 160 }, 161 161 ): Promise<RegionCheckerResponse> { 162 162 // 163 - const res = await fetch(`https://checker.openstatus.dev/ping/${region}`, { 163 + // 164 + const regionInfo = regionDict[region]; 165 + 166 + let endpoint = ""; 167 + let regionHeader = {}; 168 + switch (regionInfo.provider) { 169 + case "fly": 170 + endpoint = `https://checker.openstatus.dev/ping/${region}`; 171 + regionHeader = { "fly-prefer-region": region }; 172 + break; 173 + case "koyeb": 174 + endpoint = `https://openstatus-checker.koyeb.app/ping/${region}`; 175 + regionHeader = { 176 + "X-KOYEB-REGION-OVERRIDE": region.replace("koyeb_", ""), 177 + }; 178 + break; 179 + default: 180 + break; 181 + } 182 + 183 + const res = await fetch(endpoint, { 164 184 headers: { 165 185 Authorization: `Basic ${process.env.CRON_SECRET}`, 166 186 "Content-Type": "application/json", 167 - "fly-prefer-region": region, 187 + ...regionHeader, 168 188 }, 169 189 method: "POST", 170 190 body: JSON.stringify({ ··· 244 264 await redis.hset(`check:base:${id}`, parsed.data); 245 265 const expire = 60 * 60 * 24 * 7; // 7days 246 266 await redis.expire(`check:base:${id}`, expire); 247 - 248 - return id; 249 267 } 250 268 251 269 export async function storeCheckerData({
+2 -5
apps/workflows/src/checker/alerting.ts
··· 6 6 selectWorkspaceSchema, 7 7 } from "@openstatus/db/src/schema"; 8 8 9 - import type { 10 - MonitorFlyRegion, 11 - Region, 12 - } from "@openstatus/db/src/schema/constants"; 9 + import type { Region } from "@openstatus/db/src/schema/constants"; 13 10 import { checkerAudit } from "../utils/audit-log"; 14 11 import { providerToFunction } from "./utils"; 15 12 ··· 192 189 }: { 193 190 monitorId: string; 194 191 status: MonitorStatus; 195 - region: MonitorFlyRegion; 192 + region: Region; 196 193 }) => { 197 194 const newData = await db 198 195 .insert(schema.monitorStatusTable)
+11 -11
packages/api/src/router/tinybird/index.ts
··· 1 1 import { z } from "zod"; 2 2 3 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 3 + import { monitorRegions } from "@openstatus/db/src/schema/constants"; 4 4 import { OSTinybird } from "@openstatus/tinybird"; 5 5 6 6 import { type SQL, and, db, eq, inArray } from "@openstatus/db"; ··· 168 168 .input( 169 169 z.object({ 170 170 monitorId: z.string(), 171 - region: z.enum(flyRegions).optional(), 171 + region: z.enum(monitorRegions).optional(), 172 172 cronTimestamp: z.number().int().optional(), 173 173 }), 174 174 ) ··· 181 181 .input( 182 182 z.object({ 183 183 monitorId: z.string(), 184 - region: z.enum(flyRegions).optional(), 184 + region: z.enum(monitorRegions).optional(), 185 185 cronTimestamp: z.number().int().optional(), 186 186 from: z.coerce.date().optional(), 187 187 to: z.coerce.date().optional(), ··· 224 224 fromDate: z.string().optional(), 225 225 toDate: z.string().optional(), 226 226 interval: z.number().int().optional(), // in minutes, default 30 227 - regions: z.enum(flyRegions).array().optional(), 227 + regions: z.enum(monitorRegions).array().optional(), 228 228 type: z.enum(types).default("http"), 229 229 period: z.enum(["7d", "30d"]).default("30d"), 230 230 }), ··· 286 286 monitorId: z.string(), 287 287 period: z.enum(periods), 288 288 type: z.enum(types).default("http"), 289 - regions: z.array(z.enum(flyRegions)).optional(), 289 + regions: z.array(z.enum(monitorRegions)).optional(), 290 290 cronTimestamp: z.number().int().optional(), 291 291 }), 292 292 ) ··· 321 321 monitorId: z.string(), 322 322 period: z.enum(periods), 323 323 type: z.enum(types).default("http"), 324 - region: z.enum(flyRegions).optional(), 324 + region: z.enum(monitorRegions).optional(), 325 325 cronTimestamp: z.number().int().optional(), 326 326 }), 327 327 ) ··· 355 355 monitorId: z.string(), 356 356 period: z.enum(periods), 357 357 type: z.enum(types).default("http"), 358 - region: z.enum(flyRegions).optional(), 358 + region: z.enum(monitorRegions).optional(), 359 359 cronTimestamp: z.number().int().optional(), 360 360 }), 361 361 ) ··· 391 391 type: z.enum(types).default("http"), 392 392 // Additional filters 393 393 interval: z.number().int().optional(), 394 - regions: z.array(z.enum(flyRegions)).optional(), 394 + regions: z.array(z.enum(monitorRegions)).optional(), 395 395 cronTimestamp: z.number().int().optional(), 396 396 }), 397 397 ) ··· 429 429 monitorIds: z.string().array(), 430 430 period: z.enum(["45d"]), 431 431 type: z.enum(types).default("http"), 432 - region: z.enum(flyRegions).optional(), 432 + region: z.enum(monitorRegions).optional(), 433 433 cronTimestamp: z.number().int().optional(), 434 434 }), 435 435 ) ··· 519 519 z.object({ 520 520 monitorId: z.string(), 521 521 period: z.enum(periods), 522 - regions: z.array(z.enum(flyRegions)).optional(), 522 + regions: z.array(z.enum(monitorRegions)).optional(), 523 523 type: z.enum(types).default("http"), 524 524 }), 525 525 ) ··· 541 541 monitorId: z.string(), 542 542 period: z.enum(periods), 543 543 interval: z.number().int().optional(), 544 - regions: z.array(z.enum(flyRegions)).optional(), 544 + regions: z.array(z.enum(monitorRegions)).optional(), 545 545 type: z.literal("http"), 546 546 }), 547 547 )
+11 -2
packages/db/src/schema/constants.ts
··· 38 38 "yyz", 39 39 ] as const; 40 40 41 + export const koyebRegions = [ 42 + "koyeb_fra", 43 + "koyeb_was", 44 + "koyeb_sin", 45 + "koyeb_tyo", 46 + "koyeb_par", 47 + "koyeb_sfo", 48 + ] as const; 49 + 41 50 export const freeFlyRegions = [ 42 51 "iad", 43 52 "ams", ··· 57 66 "other", 58 67 ] as const; 59 68 60 - export const monitorRegions = [...flyRegions] as const; 69 + export const monitorRegions = [...flyRegions, ...koyebRegions] as const; 61 70 export const monitorPeriodicitySchema = z.enum(monitorPeriodicity); 62 71 export const monitorRegionSchema = z.enum(monitorRegions); 63 72 export const monitorFlyRegionSchema = z.enum(flyRegions); 64 73 65 74 export type MonitorFlyRegion = z.infer<typeof monitorFlyRegionSchema>; 66 - export type Region = z.infer<typeof monitorFlyRegionSchema>; 75 + export type Region = z.infer<typeof monitorRegionSchema>;
+4 -75
packages/db/src/schema/plan/config.ts
··· 1 + import { type Region, monitorRegions } from "../constants"; 1 2 import type { WorkspacePlan } from "../workspaces/validation"; 2 3 import type { Limits } from "./schema"; 3 4 ··· 50 51 "notification-channels": 1, 51 52 members: 1, 52 53 "audit-log": false, 53 - regions: ["ams", "gru", "iad", "jnb", "hkg", "syd"], 54 + regions: ["ams", "gru", "iad", "jnb", "hkg", "syd"] satisfies Region[], 54 55 "private-locations": false, 55 56 }, 56 57 }, ··· 88 89 "notification-channels": 10, 89 90 members: "Unlimited", 90 91 "audit-log": false, 91 - regions: [ 92 - "ams", 93 - "arn", 94 - "atl", 95 - "bog", 96 - "bom", 97 - "bos", 98 - "cdg", 99 - "den", 100 - "dfw", 101 - "ewr", 102 - "eze", 103 - "fra", 104 - "gdl", 105 - "gig", 106 - "gru", 107 - "hkg", 108 - "iad", 109 - "jnb", 110 - "lax", 111 - "lhr", 112 - "mad", 113 - "mia", 114 - "nrt", 115 - "ord", 116 - "otp", 117 - "phx", 118 - "qro", 119 - "scl", 120 - "sea", 121 - "sin", 122 - "sjc", 123 - "syd", 124 - "waw", 125 - "yul", 126 - "yyz", 127 - ], 92 + regions: [...monitorRegions], 128 93 "private-locations": false, 129 94 }, 130 95 }, ··· 162 127 "notification-channels": 20, 163 128 members: "Unlimited", 164 129 "audit-log": true, 165 - regions: [ 166 - "ams", 167 - "arn", 168 - "atl", 169 - "bog", 170 - "bom", 171 - "bos", 172 - "cdg", 173 - "den", 174 - "dfw", 175 - "ewr", 176 - "eze", 177 - "fra", 178 - "gdl", 179 - "gig", 180 - "gru", 181 - "hkg", 182 - "iad", 183 - "jnb", 184 - "lax", 185 - "lhr", 186 - "mad", 187 - "mia", 188 - "nrt", 189 - "ord", 190 - "otp", 191 - "phx", 192 - "qro", 193 - "scl", 194 - "sea", 195 - "sin", 196 - "sjc", 197 - "syd", 198 - "waw", 199 - "yul", 200 - "yyz", 201 - ], 130 + regions: [...monitorRegions], 202 131 "private-locations": false, 203 132 }, 204 133 },
+2 -2
packages/db/src/schema/plan/schema.ts
··· 1 1 import { z } from "zod"; 2 - import { monitorFlyRegionSchema, monitorPeriodicitySchema } from "../constants"; 2 + import { monitorPeriodicitySchema, monitorRegionSchema } from "../constants"; 3 3 4 4 // REMINDER: this is not a database table but just a schema for the limits of the plan 5 5 // default values are set to the free plan limits ··· 17 17 "data-retention": z 18 18 .enum(["14 days", "3 months", "12 months", "24 months"]) 19 19 .default("14 days"), 20 - regions: monitorFlyRegionSchema 20 + regions: monitorRegionSchema 21 21 .array() 22 22 .default(["ams", "gru", "iad", "jnb", "hkg", "syd"]), 23 23 "private-locations": z.boolean().default(false),
+41
packages/icons/src/fly.tsx
··· 1 + export function Fly(props: React.ComponentProps<"svg">) { 2 + return ( 3 + <svg 4 + viewBox="0 0 167 151" 5 + xmlns="http://www.w3.org/2000/svg" 6 + fillRule="evenodd" 7 + clipRule="evenodd" 8 + strokeLinejoin="round" 9 + strokeMiterlimit="2" 10 + {...props} 11 + > 12 + <title>Fly</title> 13 + <path 14 + d="M116.78 20.613h19.23c17.104 0 30.99 13.886 30.99 30.99v67.618c0 17.104-13.886 30.99-30.99 30.99h-1.516c-8.803-1.377-12.621-4.017-15.57-6.248L94.475 123.86a3.453 3.453 0 00-4.329 0l-7.943 6.532-22.37-18.394a3.443 3.443 0 00-4.326 0l-31.078 27.339c-6.255 5.087-10.392 4.148-13.075 3.853C4.424 137.503 0 128.874 0 119.221V51.603c0-17.104 13.886-30.99 30.993-30.99H50.18l-.035.077-.647 1.886-.201.647-.871 3.862-.12.678-.382 3.868-.051 1.062-.008.372.036 1.774.088 1.039.215 1.628.275 1.464.326 1.349.423 1.46 1.098 3.092.362.927 1.912 4.04.675 1.241 2.211 3.795.846 1.369 3.086 4.544.446.602 4.015 5.226 1.297 1.608 4.585 5.36.942 1.031 3.779 4.066 1.497 1.55 2.474 2.457-.497.415-.309.279a30.309 30.309 0 00-2.384 2.49c-.359.423-.701.86-1.025 1.31-.495.687-.938 1.41-1.324 2.164-.198.391-.375.792-.531 1.202a11.098 11.098 0 00-.718 3.267l-.014.966c.035 1.362.312 2.707.819 3.972a11.06 11.06 0 002.209 3.464 11.274 11.274 0 002.329 1.896c.731.447 1.51.816 2.319 1.096 1.76.597 3.627.809 5.476.623h.01a12.347 12.347 0 004.516-1.341 11.647 11.647 0 001.724-1.116 11.067 11.067 0 003.479-4.626c.569-1.422.848-2.941.823-4.471l-.044-.799a11.305 11.305 0 00-.749-3.078c-.17-.429-.364-.848-.58-1.257-.4-.752-.856-1.473-1.362-2.158-.232-.313-.472-.62-.72-.921a29.81 29.81 0 00-2.661-2.787l-.669-.569 1.133-1.119 4.869-5.085 1.684-1.849 2.618-2.945 1.703-1.992 2.428-2.957 1.644-2.067 2.414-3.228 1.219-1.67 1.729-2.585 1.44-2.203 2.713-4.725 1.552-3.1.045-.095 1.188-2.876c.015-.037.029-.075.04-.114l1.28-3.991.134-.582.555-3.177.108-.86.033-.527.038-1.989-.01-.371-.102-1.781-.126-1.383-.63-3.989a1.521 1.521 0 00-.037-.159l-.809-2.949-.279-.82-.364-.907zm9.141 84.321c-4.007.056-7.287 3.336-7.343 7.342.059 4.006 3.337 7.284 7.343 7.341 4.005-.058 7.284-3.335 7.345-7.341-.058-4.006-3.338-7.286-7.345-7.342z" 15 + fill="url(#_Radial1)" 16 + /> 17 + <path 18 + d="M72.499 147.571l-1.296 1.09a6.802 6.802 0 01-4.253 1.55H30.993a30.867 30.867 0 01-19.639-7.021c2.683.295 6.82 1.234 13.075-3.853l31.078-27.339a3.443 3.443 0 014.326 0l22.37 18.394 7.943-6.532a3.453 3.453 0 014.329 0l24.449 20.103c2.949 2.231 6.767 4.871 15.57 6.248H118.23a6.919 6.919 0 01-3.993-1.33l-.285-.22-1.207-1.003a2.377 2.377 0 00-.32-.323 21845.256 21845.256 0 00-18.689-15.497 2.035 2.035 0 00-2.606.006s.044.052-18.386 15.491c-.09.075-.172.154-.245.236zm53.422-42.637c-4.007.056-7.287 3.336-7.343 7.342.059 4.006 3.337 7.284 7.343 7.341 4.005-.058 7.284-3.335 7.345-7.341-.058-4.006-3.338-7.286-7.345-7.342zM78.453 82.687l-2.474-2.457-1.497-1.55-3.779-4.066-.942-1.031-4.585-5.36-1.297-1.609-4.015-5.225-.446-.602-3.086-4.544-.846-1.369-2.211-3.795-.675-1.241-1.912-4.04-.362-.927-1.098-3.092-.423-1.46-.326-1.349-.275-1.465-.215-1.627-.088-1.039-.036-1.774.008-.372.051-1.062.382-3.869.12-.677.871-3.862.201-.647.647-1.886.207-.488 1.03-2.262.714-1.346.994-1.64.991-1.46.706-.928.813-.98.895-.985.767-.771 1.867-1.643 1.365-1.117c.033-.028.067-.053.102-.077l1.615-1.092 1.283-.818L65.931 3.8c.037-.023.079-.041.118-.059l3.456-1.434.319-.12 3.072-.899 1.297-.291 1.754-.352L77.11.468l1.784-.222L80.11.138 82.525.01l.946-.01 1.791.037.466.026 2.596.216 3.433.484.397.083 3.393.844.996.297 1.107.383 1.348.51 1.066.452 1.566.738.987.507 1.774 1.041.661.407 2.418 1.765.694.602 1.686 1.536.083.083 1.43 1.534.492.555 1.678 2.23.342.533 1.332 2.249.401.771.751 1.678.785 1.959.279.82.809 2.949c.015.052.027.105.037.159l.63 3.988.126 1.384.102 1.781.01.371-.038 1.989-.033.527-.108.86-.555 3.177-.134.582-1.28 3.991a1.186 1.186 0 01-.04.114l-1.188 2.876-.045.095-1.552 3.1-2.713 4.725-1.44 2.203-1.729 2.585-1.219 1.67-2.414 3.228-1.644 2.067-2.428 2.957-1.703 1.992-2.618 2.945-1.684 1.849-4.869 5.085-1.133 1.119.669.569c.946.871 1.835 1.8 2.661 2.787.248.301.488.608.72.921.506.685.962 1.406 1.362 2.158.216.407.409.828.58 1.257.389.985.651 2.026.749 3.078l.044.799c.025 1.53-.255 3.05-.823 4.471a11.057 11.057 0 01-3.479 4.625c-.541.424-1.118.796-1.724 1.117a12.347 12.347 0 01-4.516 1.341h-.01a12.996 12.996 0 01-5.476-.623 11.933 11.933 0 01-2.319-1.096 11.268 11.268 0 01-2.329-1.896 11.06 11.06 0 01-2.209-3.464 11.468 11.468 0 01-.819-3.972l.014-.966c.073-1.119.315-2.221.718-3.267.157-.411.334-.812.531-1.202.386-.755.83-1.477 1.324-2.164.323-.45.667-.887 1.025-1.31a30.309 30.309 0 012.384-2.49l.309-.279.497-.415z" 19 + fill="#24175b" 20 + /> 21 + <path 22 + d="M71.203 148.661l19.927-16.817a2.035 2.035 0 012.606-.006l20.216 16.823a6.906 6.906 0 004.351 1.55H66.877a6.805 6.805 0 004.326-1.55zm12.404-60.034l.195.057c.063.03.116.075.173.114l.163.144c.402.37.793.759 1.169 1.157.265.283.523.574.771.875.315.38.61.779.879 1.194.116.183.224.368.325.561.088.167.167.34.236.515.122.305.214.627.242.954l-.006.614a3.507 3.507 0 01-1.662 2.732 4.747 4.747 0 01-2.021.665l-.759.022-.641-.056a4.964 4.964 0 01-.881-.214 4.17 4.17 0 01-.834-.391l-.5-.366a3.431 3.431 0 01-1.139-1.952 5.016 5.016 0 01-.059-.387l-.018-.586c.01-.158.034-.315.069-.472.087-.341.213-.673.372-.988.205-.396.439-.776.7-1.137.433-.586.903-1.143 1.405-1.67.324-.342.655-.673 1.001-.993l.246-.221c.171-.114.173-.114.368-.171h.206zM82.348 6.956l.079-.006v68.484l-.171-.315a191.264 191.264 0 01-6.291-12.75 136.318 136.318 0 01-4.269-10.688 84.358 84.358 0 01-2.574-8.802c-.541-2.365-.956-4.765-1.126-7.19a35.028 35.028 0 01-.059-3.108c.016-.903.053-1.804.109-2.705.09-1.418.234-2.832.442-4.235.165-1.104.368-2.205.62-3.293.2-.865.431-1.723.696-2.567.382-1.22.84-2.412 1.373-3.576.195-.419.405-.836.624-1.245 1.322-2.449 3.116-4.704 5.466-6.214a11.422 11.422 0 015.081-1.79zm8.88.173l4.607 1.314a28.193 28.193 0 016.076 3.096 24.387 24.387 0 016.533 6.517 24.618 24.618 0 012.531 4.878 28.586 28.586 0 011.761 7.898c.061.708.096 1.418.11 2.127.016.659.012 1.321-.041 1.98a22.306 22.306 0 01-.828 4.352 34.281 34.281 0 01-1.194 3.426 49.43 49.43 0 01-1.895 4.094c-1.536 2.966-3.304 5.803-5.195 8.547a133.118 133.118 0 01-7.491 9.776 185.466 185.466 0 01-8.987 9.96c2.114-3.963 4.087-8 5.915-12.102a149.96 149.96 0 002.876-6.93 108.799 108.799 0 002.679-7.792 76.327 76.327 0 001.54-5.976c.368-1.727.657-3.472.836-5.228.15-1.464.205-2.937.169-4.406a62.154 62.154 0 00-.1-2.695c-.216-3.612-.765-7.212-1.818-10.676a31.255 31.255 0 00-1.453-3.849c-1.348-2.937-3.23-5.683-5.776-7.686l-.855-.625z" 23 + fill="#fff" 24 + /> 25 + <defs> 26 + <radialGradient 27 + id="_Radial1" 28 + cx="0" 29 + cy="0" 30 + r="1" 31 + gradientUnits="userSpaceOnUse" 32 + gradientTransform="translate(88.67 84.848) scale(120.977)" 33 + > 34 + <stop offset="0" stopColor="#ba7bf0" /> 35 + <stop offset=".45" stopColor="#996bec" /> 36 + <stop offset="1" stopColor="#5046e4" /> 37 + </radialGradient> 38 + </defs> 39 + </svg> 40 + ); 41 + }
+3
packages/icons/src/index.tsx
··· 4 4 export * from "./pagerduty"; 5 5 export * from "./slack"; 6 6 export * from "./opsgenie"; 7 + export * from "./fly"; 8 + export * from "./railway"; 9 + export * from "./koyeb";
+29
packages/icons/src/koyeb.tsx
··· 1 + export function Koyeb(props: React.ComponentProps<"svg">) { 2 + return ( 3 + <svg 4 + width="485" 5 + height="480" 6 + viewBox="0 0 485 480" 7 + fill="none" 8 + xmlns="http://www.w3.org/2000/svg" 9 + {...props} 10 + > 11 + <title>Koyeb</title> 12 + <path 13 + d="M242.171 116.666L484.653 256.284V139.774L242.016 0L0 139.929V256.594L242.171 116.666Z" 14 + fill="currentColor" 15 + className="fill-[#100F13] dark:fill-white" 16 + /> 17 + <path 18 + d="M445.867 396.691L484.653 373.88V302.359L242.016 162.43L0 302.359V374.191L38.785 396.691L242.016 279.095L445.867 396.691Z" 19 + fill="currentColor" 20 + className="fill-[#100F13] dark:fill-white" 21 + /> 22 + <path 23 + d="M242.17 444.471L303.753 479.999L404.91 421.815L242.015 327.805L79.7402 421.815L180.884 479.999L242.17 444.471Z" 24 + fill="currentColor" 25 + className="fill-[#100F13] dark:fill-white" 26 + /> 27 + </svg> 28 + ); 29 + }
+24
packages/icons/src/railway.tsx
··· 1 + export function Railway(props: React.ComponentProps<"svg">) { 2 + return ( 3 + <svg 4 + width="1024" 5 + height="1024" 6 + viewBox="0 0 1024 1024" 7 + fill="none" 8 + xmlns="http://www.w3.org/2000/svg" 9 + {...props} 10 + > 11 + <title>Railway</title> 12 + <path 13 + d="M4.756 438.175A520.713 520.713 0 0 0 0 489.735h777.799c-2.716-5.306-6.365-10.09-10.045-14.772-132.97-171.791-204.498-156.896-306.819-161.26-34.114-1.403-57.249-1.967-193.037-1.967-72.677 0-151.688.185-228.628.39-9.96 26.884-19.566 52.942-24.243 74.14h398.571v51.909H4.756ZM783.93 541.696H.399c.82 13.851 2.112 27.517 3.978 40.999h723.39c32.248 0 50.299-18.297 56.162-40.999ZM45.017 724.306S164.941 1018.77 511.46 1024c207.112 0 385.071-123.006 465.907-299.694H45.017Z" 14 + fill="currentColor" 15 + className="fill-[#100F13] dark:fill-white" 16 + /> 17 + <path 18 + d="M511.454 0C319.953 0 153.311 105.16 65.31 260.612c68.771-.144 202.704-.226 202.704-.226h.031v-.051c158.309 0 164.193.707 195.118 1.998l19.149.706c66.7 2.224 148.683 9.384 213.19 58.19 35.015 26.471 85.571 84.896 115.708 126.52 27.861 38.499 35.876 82.756 16.933 125.158-17.436 38.97-54.952 62.215-100.383 62.215H16.69s4.233 17.944 10.58 37.751h970.632A510.385 510.385 0 0 0 1024 512.218C1024.01 229.355 794.532 0 511.454 0Z" 19 + fill="currentColor" 20 + className="fill-[#100F13] dark:fill-white" 21 + /> 22 + </svg> 23 + ); 24 + }
+2 -2
packages/notifications/email/src/index.ts
··· 6 6 7 7 import type { Region } from "@openstatus/db/src/schema/constants"; 8 8 import { EmailClient } from "@openstatus/emails/src/client"; 9 - import { flyRegionsDict } from "@openstatus/utils"; 9 + import { regionDict } from "@openstatus/utils"; 10 10 import { env } from "../env"; 11 11 12 12 const emailClient = new EmailClient({ apiKey: env.RESEND_API_KEY }); ··· 40 40 url: monitor.url, 41 41 status: statusCode?.toString(), 42 42 latency: latency ? `${latency}ms` : "N/A", 43 - region: region ? flyRegionsDict[region].location : "N/A", 43 + region: region ? regionDict[region].location : "N/A", 44 44 timestamp: new Date(cronTimestamp).toISOString(), 45 45 message, 46 46 });
+62 -62
packages/tinybird/src/client.ts
··· 1 1 import { Tinybird as Client, NoopTinybird } from "@chronark/zod-bird"; 2 2 import { z } from "zod"; 3 - import { flyRegions } from "../../db/src/schema/constants"; 3 + import { monitorRegions } from "../../db/src/schema/constants"; 4 4 import { 5 5 headersSchema, 6 6 timingPhasesSchema, ··· 51 51 statusCode: z.number().int().nullable(), 52 52 monitorId: z.string(), 53 53 error: z.coerce.boolean(), 54 - region: z.enum(flyRegions), 54 + region: z.enum(monitorRegions), 55 55 cronTimestamp: z.number().int(), 56 56 trigger: z.enum(triggers).nullable().default("cron"), 57 57 timestamp: z.number(), ··· 76 76 statusCode: z.number().int().nullable(), 77 77 monitorId: z.string(), 78 78 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 79 - region: z.enum(flyRegions), 79 + region: z.enum(monitorRegions), 80 80 cronTimestamp: z.number().int(), 81 81 trigger: z.enum(triggers).nullable().default("cron"), 82 82 timestamp: z.number(), ··· 98 98 statusCode: z.number().int().nullable(), 99 99 monitorId: z.string(), 100 100 error: z.coerce.boolean(), 101 - region: z.enum(flyRegions), 101 + region: z.enum(monitorRegions), 102 102 cronTimestamp: z.number().int(), 103 103 trigger: z.enum(triggers).nullable().default("cron"), 104 104 timestamp: z.number(), ··· 123 123 statusCode: z.number().int().nullable(), 124 124 monitorId: z.string(), 125 125 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 126 - region: z.enum(flyRegions), 126 + region: z.enum(monitorRegions), 127 127 cronTimestamp: z.number().int(), 128 128 trigger: z.enum(triggers).nullable().default("cron"), 129 129 timestamp: z.number(), ··· 145 145 statusCode: z.number().int().nullable(), 146 146 monitorId: z.string(), 147 147 error: z.coerce.boolean(), 148 - region: z.enum(flyRegions), 148 + region: z.enum(monitorRegions), 149 149 cronTimestamp: z.number().int(), 150 150 trigger: z.enum(triggers).nullable().default("cron"), 151 151 timestamp: z.number(), ··· 170 170 statusCode: z.number().int().nullable(), 171 171 monitorId: z.string(), 172 172 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 173 - region: z.enum(flyRegions), 173 + region: z.enum(monitorRegions), 174 174 cronTimestamp: z.number().int(), 175 175 trigger: z.enum(triggers).nullable().default("cron"), 176 176 timestamp: z.number(), ··· 184 184 return this.tb.buildPipe({ 185 185 pipe: "endpoint__http_metrics_1d__v0", 186 186 parameters: z.object({ 187 - regions: z.array(z.enum(flyRegions)).optional(), 187 + regions: z.array(z.enum(monitorRegions)).optional(), 188 188 interval: z.number().int().optional(), 189 189 monitorId: z.string(), 190 190 }), ··· 207 207 pipe: "endpoint__http_metrics_1d__v1", 208 208 parameters: z.object({ 209 209 interval: z.number().int().optional(), 210 - regions: z.array(z.enum(flyRegions)).optional(), 210 + regions: z.array(z.enum(monitorRegions)).optional(), 211 211 monitorId: z.string(), 212 212 }), 213 213 data: z.object({ ··· 230 230 return this.tb.buildPipe({ 231 231 pipe: "endpoint__http_metrics_7d__v0", 232 232 parameters: z.object({ 233 - regions: z.array(z.enum(flyRegions)).optional(), 233 + regions: z.array(z.enum(monitorRegions)).optional(), 234 234 interval: z.number().int().optional(), 235 235 monitorId: z.string(), 236 236 }), ··· 253 253 pipe: "endpoint__http_metrics_7d__v1", 254 254 parameters: z.object({ 255 255 interval: z.number().int().optional(), 256 - regions: z.array(z.enum(flyRegions)).optional(), 256 + regions: z.array(z.enum(monitorRegions)).optional(), 257 257 monitorId: z.string(), 258 258 }), 259 259 data: z.object({ ··· 276 276 return this.tb.buildPipe({ 277 277 pipe: "endpoint__http_metrics_14d__v0", 278 278 parameters: z.object({ 279 - regions: z.array(z.enum(flyRegions)).optional(), 279 + regions: z.array(z.enum(monitorRegions)).optional(), 280 280 interval: z.number().int().optional(), 281 281 monitorId: z.string(), 282 282 }), ··· 299 299 pipe: "endpoint__http_metrics_14d__v1", 300 300 parameters: z.object({ 301 301 interval: z.number().int().optional(), 302 - regions: z.array(z.enum(flyRegions)).optional(), 302 + regions: z.array(z.enum(monitorRegions)).optional(), 303 303 monitorId: z.string(), 304 304 }), 305 305 data: z.object({ ··· 326 326 monitorId: z.string(), 327 327 }), 328 328 data: z.object({ 329 - region: z.enum(flyRegions), 329 + region: z.enum(monitorRegions), 330 330 timestamp: z.number().int(), 331 331 p50Latency: z.number().nullable().default(0), 332 332 p75Latency: z.number().nullable().default(0), ··· 346 346 monitorId: z.string(), 347 347 }), 348 348 data: z.object({ 349 - region: z.enum(flyRegions), 349 + region: z.enum(monitorRegions), 350 350 timestamp: z.number().int(), 351 351 p50Latency: z.number().nullable().default(0), 352 352 p75Latency: z.number().nullable().default(0), ··· 366 366 monitorId: z.string(), 367 367 }), 368 368 data: z.object({ 369 - region: z.enum(flyRegions), 369 + region: z.enum(monitorRegions), 370 370 timestamp: z.number().int(), 371 371 p50Latency: z.number().nullable().default(0), 372 372 p75Latency: z.number().nullable().default(0), ··· 383 383 pipe: "endpoint__http_metrics_by_region_1d__v0", 384 384 parameters: z.object({ 385 385 monitorId: z.string(), 386 - regions: z.array(z.enum(flyRegions)).optional(), 386 + regions: z.array(z.enum(monitorRegions)).optional(), 387 387 }), 388 388 data: z.object({ 389 - region: z.enum(flyRegions), 389 + region: z.enum(monitorRegions), 390 390 count: z.number().int(), 391 391 ok: z.number().int(), 392 392 p50Latency: z.number().nullable().default(0), ··· 404 404 pipe: "endpoint__http_metrics_by_region_7d__v0", 405 405 parameters: z.object({ 406 406 monitorId: z.string(), 407 - regions: z.array(z.enum(flyRegions)).optional(), 407 + regions: z.array(z.enum(monitorRegions)).optional(), 408 408 }), 409 409 data: z.object({ 410 - region: z.enum(flyRegions), 410 + region: z.enum(monitorRegions), 411 411 count: z.number().int(), 412 412 ok: z.number().int(), 413 413 p50Latency: z.number().nullable().default(0), ··· 425 425 pipe: "endpoint__http_metrics_by_region_14d__v0", 426 426 parameters: z.object({ 427 427 monitorId: z.string(), 428 - regions: z.array(z.enum(flyRegions)).optional(), 428 + regions: z.array(z.enum(monitorRegions)).optional(), 429 429 }), 430 430 data: z.object({ 431 - region: z.enum(flyRegions), 431 + region: z.enum(monitorRegions), 432 432 count: z.number().int(), 433 433 ok: z.number().int(), 434 434 p50Latency: z.number().nullable().default(0), ··· 518 518 monitorId: z.string(), 519 519 url: z.string().url(), 520 520 error: z.coerce.boolean(), 521 - region: z.enum(flyRegions), 521 + region: z.enum(monitorRegions), 522 522 cronTimestamp: z.number().int(), 523 523 message: z.string().nullable(), 524 524 headers: headersSchema, ··· 540 540 pipe: "endpoint__http_get_30d__v0", 541 541 parameters: z.object({ 542 542 monitorId: z.string(), 543 - region: z.enum(flyRegions).optional(), 543 + region: z.enum(monitorRegions).optional(), 544 544 cronTimestamp: z.number().int().optional(), 545 545 }), 546 546 data: z.object({ ··· 550 550 monitorId: z.string(), 551 551 url: z.string().url(), 552 552 error: z.coerce.boolean(), 553 - region: z.enum(flyRegions), 553 + region: z.enum(monitorRegions), 554 554 cronTimestamp: z.number().int(), 555 555 message: z.string().nullable(), 556 556 headers: headersSchema, ··· 583 583 .number() 584 584 .default(0) 585 585 .transform((val) => val !== 0), 586 - region: z.enum(flyRegions), 586 + region: z.enum(monitorRegions), 587 587 timestamp: z.number().int().optional(), 588 588 message: z.string().nullable().optional(), 589 589 timing: timingSchema, ··· 605 605 latency: z.number().int(), 606 606 monitorId: z.coerce.string(), 607 607 error: z.coerce.boolean(), 608 - region: z.enum(flyRegions), 608 + region: z.enum(monitorRegions), 609 609 cronTimestamp: z.number().int(), 610 610 trigger: z.enum(triggers).nullable().default("cron"), 611 611 timestamp: z.number(), ··· 629 629 latency: z.number().int(), 630 630 monitorId: z.coerce.string(), 631 631 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 632 - region: z.enum(flyRegions), 632 + region: z.enum(monitorRegions), 633 633 cronTimestamp: z.number().int(), 634 634 trigger: z.enum(triggers).nullable().default("cron"), 635 635 timestamp: z.number(), ··· 649 649 latency: z.number().int(), 650 650 monitorId: z.coerce.string(), 651 651 error: z.coerce.boolean(), 652 - region: z.enum(flyRegions), 652 + region: z.enum(monitorRegions), 653 653 cronTimestamp: z.number().int(), 654 654 trigger: z.enum(triggers).nullable().default("cron"), 655 655 timestamp: z.number(), ··· 673 673 latency: z.number().int(), 674 674 monitorId: z.coerce.string(), 675 675 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 676 - region: z.enum(flyRegions), 676 + region: z.enum(monitorRegions), 677 677 cronTimestamp: z.number().int(), 678 678 trigger: z.enum(triggers).nullable().default("cron"), 679 679 timestamp: z.number(), ··· 693 693 latency: z.number().int(), 694 694 monitorId: z.coerce.string(), 695 695 error: z.coerce.boolean(), 696 - region: z.enum(flyRegions), 696 + region: z.enum(monitorRegions), 697 697 cronTimestamp: z.number().int(), 698 698 trigger: z.enum(triggers).nullable().default("cron"), 699 699 timestamp: z.number(), ··· 717 717 latency: z.number().int(), 718 718 monitorId: z.coerce.string(), 719 719 requestStatus: z.enum(["error", "success", "degraded"]).nullable(), 720 - region: z.enum(flyRegions), 720 + region: z.enum(monitorRegions), 721 721 cronTimestamp: z.number().int(), 722 722 trigger: z.enum(triggers).nullable().default("cron"), 723 723 timestamp: z.number(), ··· 730 730 return this.tb.buildPipe({ 731 731 pipe: "endpoint__tcp_metrics_1d__v0", 732 732 parameters: z.object({ 733 - regions: z.array(z.enum(flyRegions)).optional(), 733 + regions: z.array(z.enum(monitorRegions)).optional(), 734 734 interval: z.number().int().optional(), 735 735 monitorId: z.string(), 736 736 }), ··· 753 753 pipe: "endpoint__tcp_metrics_1d__v1", 754 754 parameters: z.object({ 755 755 interval: z.number().int().optional(), 756 - regions: z.array(z.enum(flyRegions)).optional(), 756 + regions: z.array(z.enum(monitorRegions)).optional(), 757 757 monitorId: z.string(), 758 758 }), 759 759 data: z.object({ ··· 777 777 pipe: "endpoint__tcp_metrics_7d__v0", 778 778 parameters: z.object({ 779 779 interval: z.number().int().optional(), 780 - regions: z.array(z.enum(flyRegions)).optional(), 780 + regions: z.array(z.enum(monitorRegions)).optional(), 781 781 monitorId: z.string(), 782 782 }), 783 783 data: z.object({ ··· 799 799 pipe: "endpoint__tcp_metrics_7d__v1", 800 800 parameters: z.object({ 801 801 interval: z.number().int().optional(), 802 - regions: z.array(z.enum(flyRegions)).optional(), 802 + regions: z.array(z.enum(monitorRegions)).optional(), 803 803 monitorId: z.string(), 804 804 }), 805 805 data: z.object({ ··· 822 822 return this.tb.buildPipe({ 823 823 pipe: "endpoint__tcp_metrics_14d__v0", 824 824 parameters: z.object({ 825 - regions: z.array(z.enum(flyRegions)).optional(), 825 + regions: z.array(z.enum(monitorRegions)).optional(), 826 826 interval: z.number().int().optional(), 827 827 monitorId: z.string(), 828 828 }), ··· 845 845 pipe: "endpoint__tcp_metrics_14d__v1", 846 846 parameters: z.object({ 847 847 interval: z.number().int().optional(), 848 - regions: z.array(z.enum(flyRegions)).optional(), 848 + regions: z.array(z.enum(monitorRegions)).optional(), 849 849 monitorId: z.string(), 850 850 }), 851 851 data: z.object({ ··· 868 868 return this.tb.buildPipe({ 869 869 pipe: "endpoint__tcp_metrics_by_interval_1d__v0", 870 870 parameters: z.object({ 871 - regions: z.array(z.enum(flyRegions)).optional(), 871 + regions: z.array(z.enum(monitorRegions)).optional(), 872 872 interval: z.number().int().optional(), 873 873 monitorId: z.string(), 874 874 }), 875 875 data: z.object({ 876 - region: z.enum(flyRegions), 876 + region: z.enum(monitorRegions), 877 877 timestamp: z.number().int(), 878 878 p50Latency: z.number().nullable().default(0), 879 879 p75Latency: z.number().nullable().default(0), ··· 889 889 return this.tb.buildPipe({ 890 890 pipe: "endpoint__tcp_metrics_by_interval_7d__v0", 891 891 parameters: z.object({ 892 - regions: z.array(z.enum(flyRegions)).optional(), 892 + regions: z.array(z.enum(monitorRegions)).optional(), 893 893 interval: z.number().int().optional(), 894 894 monitorId: z.string(), 895 895 }), 896 896 data: z.object({ 897 - region: z.enum(flyRegions), 897 + region: z.enum(monitorRegions), 898 898 timestamp: z.number().int(), 899 899 p50Latency: z.number().nullable().default(0), 900 900 p75Latency: z.number().nullable().default(0), ··· 910 910 return this.tb.buildPipe({ 911 911 pipe: "endpoint__tcp_metrics_by_interval_14d__v0", 912 912 parameters: z.object({ 913 - regions: z.array(z.enum(flyRegions)).optional(), 913 + regions: z.array(z.enum(monitorRegions)).optional(), 914 914 interval: z.number().int().optional(), 915 915 monitorId: z.string(), 916 916 }), 917 917 data: z.object({ 918 - region: z.enum(flyRegions), 918 + region: z.enum(monitorRegions), 919 919 timestamp: z.number().int(), 920 920 p50Latency: z.number().nullable().default(0), 921 921 p75Latency: z.number().nullable().default(0), ··· 932 932 pipe: "endpoint__tcp_metrics_by_region_1d__v0", 933 933 parameters: z.object({ 934 934 monitorId: z.string(), 935 - regions: z.array(z.enum(flyRegions)).optional(), 935 + regions: z.array(z.enum(monitorRegions)).optional(), 936 936 }), 937 937 data: z.object({ 938 - region: z.enum(flyRegions), 938 + region: z.enum(monitorRegions), 939 939 count: z.number().int(), 940 940 ok: z.number().int(), 941 941 p50Latency: z.number().nullable().default(0), ··· 953 953 pipe: "endpoint__tcp_metrics_by_region_7d__v0", 954 954 parameters: z.object({ 955 955 monitorId: z.string(), 956 - regions: z.array(z.enum(flyRegions)).optional(), 956 + regions: z.array(z.enum(monitorRegions)).optional(), 957 957 }), 958 958 data: z.object({ 959 - region: z.enum(flyRegions), 959 + region: z.enum(monitorRegions), 960 960 count: z.number().int(), 961 961 ok: z.number().int(), 962 962 p50Latency: z.number().nullable().default(0), ··· 974 974 pipe: "endpoint__tcp_metrics_by_region_14d__v0", 975 975 parameters: z.object({ 976 976 monitorId: z.string(), 977 - regions: z.array(z.enum(flyRegions)).optional(), 977 + regions: z.array(z.enum(monitorRegions)).optional(), 978 978 }), 979 979 data: z.object({ 980 - region: z.enum(flyRegions), 980 + region: z.enum(monitorRegions), 981 981 count: z.number().int(), 982 982 ok: z.number().int(), 983 983 p50Latency: z.number().nullable().default(0), ··· 1099 1099 latency: z.number().int(), 1100 1100 monitorId: z.coerce.string(), 1101 1101 error: z.coerce.boolean(), 1102 - region: z.enum(flyRegions), 1102 + region: z.enum(monitorRegions), 1103 1103 cronTimestamp: z.number().int(), 1104 1104 trigger: z.enum(triggers).nullable().default("cron"), 1105 1105 timestamp: z.number(), ··· 1116 1116 pipe: "endpoint__tcp_get_30d__v0", 1117 1117 parameters: z.object({ 1118 1118 monitorId: z.string(), 1119 - region: z.enum(flyRegions).optional(), 1119 + region: z.enum(monitorRegions).optional(), 1120 1120 cronTimestamp: z.number().int().optional(), 1121 1121 }), 1122 1122 data: z.object({ ··· 1124 1124 latency: z.number().int(), 1125 1125 monitorId: z.string(), 1126 1126 error: z.coerce.boolean(), 1127 - region: z.enum(flyRegions), 1127 + region: z.enum(monitorRegions), 1128 1128 cronTimestamp: z.number().int(), 1129 1129 trigger: z.enum(triggers).nullable().default("cron"), 1130 1130 timestamp: z.number(), ··· 1149 1149 regions: z.string().array().optional(), 1150 1150 }), 1151 1151 data: z.object({ 1152 - region: z.enum(flyRegions), 1152 + region: z.enum(monitorRegions), 1153 1153 timestamp: z.number().int(), 1154 1154 p50Latency: z.number().nullable().default(0), 1155 1155 p75Latency: z.number().nullable().default(0), ··· 1170 1170 regions: z.string().array().optional(), 1171 1171 }), 1172 1172 data: z.object({ 1173 - region: z.enum(flyRegions), 1173 + region: z.enum(monitorRegions), 1174 1174 timestamp: z.number().int(), 1175 1175 p50Latency: z.number().nullable().default(0), 1176 1176 p75Latency: z.number().nullable().default(0), ··· 1191 1191 regions: z.string().array().optional(), 1192 1192 }), 1193 1193 data: z.object({ 1194 - region: z.enum(flyRegions), 1194 + region: z.enum(monitorRegions), 1195 1195 timestamp: z.number().int(), 1196 1196 p50Latency: z.number().nullable().default(0), 1197 1197 p75Latency: z.number().nullable().default(0), ··· 1210 1210 monitorId: z.string(), 1211 1211 fromDate: z.string().optional(), 1212 1212 toDate: z.string().optional(), 1213 - regions: z.enum(flyRegions).array().optional(), 1213 + regions: z.enum(monitorRegions).array().optional(), 1214 1214 interval: z.number().int().optional(), 1215 1215 }), 1216 1216 data: z.object({ ··· 1229 1229 monitorId: z.string(), 1230 1230 fromDate: z.string().optional(), 1231 1231 toDate: z.string().optional(), 1232 - regions: z.enum(flyRegions).array().optional(), 1232 + regions: z.enum(monitorRegions).array().optional(), 1233 1233 interval: z.number().int().optional(), 1234 1234 }), 1235 1235 data: z.object({ ··· 1248 1248 monitorId: z.string(), 1249 1249 fromDate: z.string().optional(), 1250 1250 toDate: z.string().optional(), 1251 - regions: z.enum(flyRegions).array().optional(), 1251 + regions: z.enum(monitorRegions).array().optional(), 1252 1252 interval: z.number().int().optional(), 1253 1253 }), 1254 1254 data: z.object({ ··· 1267 1267 monitorId: z.string(), 1268 1268 fromDate: z.string().optional(), 1269 1269 toDate: z.string().optional(), 1270 - regions: z.enum(flyRegions).array().optional(), 1270 + regions: z.enum(monitorRegions).array().optional(), 1271 1271 interval: z.number().int().optional(), 1272 1272 }), 1273 1273 data: z.object({ ··· 1353 1353 parameters: z.object({ 1354 1354 monitorId: z.string(), 1355 1355 interval: z.number().int().optional(), 1356 - regions: z.array(z.enum(flyRegions)).optional(), 1356 + regions: z.array(z.enum(monitorRegions)).optional(), 1357 1357 }), 1358 1358 data: z.object({ 1359 1359 timestamp: z.number().int(), ··· 1444 1444 pipe: "endpoint__tcp_metrics_latency_1d__v1", 1445 1445 parameters: z.object({ 1446 1446 monitorId: z.string(), 1447 - regions: z.array(z.enum(flyRegions)).optional(), 1447 + regions: z.array(z.enum(monitorRegions)).optional(), 1448 1448 }), 1449 1449 data: z.object({ 1450 1450 timestamp: z.number().int(),
+83 -24
packages/utils/index.ts
··· 3 3 * https://vercel.com/docs/concepts/edge-network/regions#region-list 4 4 */ 5 5 6 - import type { MonitorFlyRegion } from "@openstatus/db/src/schema/constants"; 6 + import type { Region } from "@openstatus/db/src/schema/constants"; 7 7 8 8 import { base } from "@openstatus/assertions"; 9 9 import { monitorMethods, monitorStatus } from "@openstatus/db/src/schema"; ··· 138 138 | "Oceania"; 139 139 140 140 export type RegionInfo = { 141 - code: MonitorFlyRegion; 141 + code: Region; 142 142 location: string; 143 143 flag: string; 144 144 continent: Continent; 145 + provider: "fly" | "koyeb" | "railway"; 145 146 }; 146 147 147 148 // TODO: we could think of doing the inverse and use "EU" as key ··· 154 155 Oceania: { code: "OC" }, 155 156 }; 156 157 157 - export const flyRegionsDict: Record<MonitorFlyRegion, RegionInfo> = { 158 + export const regionDict: Record<Region, RegionInfo> = { 158 159 ams: { 159 160 code: "ams", 160 161 location: "Amsterdam, Netherlands", 161 162 flag: "๐Ÿ‡ณ๐Ÿ‡ฑ", 162 163 continent: "Europe", 164 + provider: "fly", 163 165 }, 164 166 arn: { 165 167 code: "arn", 166 168 location: "Stockholm, Sweden", 167 169 flag: "๐Ÿ‡ธ๐Ÿ‡ช", 168 170 continent: "Europe", 171 + provider: "fly", 169 172 }, 170 173 171 174 atl: { ··· 173 176 location: "Atlanta, Georgia, USA", 174 177 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 175 178 continent: "North America", 179 + provider: "fly", 176 180 }, 177 181 bog: { 178 182 code: "bog", 179 183 location: "Bogotรก, Colombia", 180 184 flag: "๐Ÿ‡จ๐Ÿ‡ด", 181 185 continent: "South America", 186 + provider: "fly", 182 187 }, 183 188 bom: { 184 189 code: "bom", 185 190 location: "Mumbai, India", 186 191 flag: "๐Ÿ‡ฎ๐Ÿ‡ณ", 187 192 continent: "Asia", 193 + provider: "fly", 188 194 }, 189 195 bos: { 190 196 code: "bos", 191 197 location: "Boston, Massachusetts, USA", 192 198 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 193 199 continent: "North America", 200 + provider: "fly", 194 201 }, 195 202 cdg: { 196 203 code: "cdg", 197 204 location: "Paris, France", 198 205 flag: "๐Ÿ‡ซ๐Ÿ‡ท", 199 206 continent: "Europe", 207 + provider: "fly", 200 208 }, 201 209 den: { 202 210 code: "den", 203 211 location: "Denver, Colorado, USA", 204 212 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 205 213 continent: "North America", 214 + provider: "fly", 206 215 }, 207 216 dfw: { 208 217 code: "dfw", 209 218 location: "Dallas, Texas, USA", 210 219 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 211 220 continent: "North America", 221 + provider: "fly", 212 222 }, 213 223 ewr: { 214 224 code: "ewr", 215 225 location: "Secaucus, New Jersey, USA", 216 226 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 217 227 continent: "North America", 228 + provider: "fly", 218 229 }, 219 230 eze: { 220 231 code: "eze", 221 232 location: "Ezeiza, Argentina", 222 233 flag: "๐Ÿ‡ฆ๐Ÿ‡ท", 223 234 continent: "South America", 235 + provider: "fly", 224 236 }, 225 237 fra: { 226 238 code: "fra", 227 239 location: "Frankfurt, Germany", 228 240 flag: "๐Ÿ‡ฉ๐Ÿ‡ช", 229 241 continent: "Europe", 242 + provider: "fly", 230 243 }, 231 244 gdl: { 232 245 code: "gdl", 233 246 location: "Guadalajara, Mexico", 234 247 flag: "๐Ÿ‡ฒ๐Ÿ‡ฝ", 235 248 continent: "North America", 249 + provider: "fly", 236 250 }, 237 251 gig: { 238 252 code: "gig", 239 253 location: "Rio de Janeiro, Brazil", 240 254 flag: "๐Ÿ‡ง๐Ÿ‡ท", 241 255 continent: "South America", 256 + provider: "fly", 242 257 }, 243 258 gru: { 244 259 code: "gru", 245 260 location: "Sao Paulo, Brazil", 246 261 flag: "๐Ÿ‡ง๐Ÿ‡ท", 247 262 continent: "South America", 263 + provider: "fly", 248 264 }, 249 265 hkg: { 250 266 code: "hkg", 251 267 location: "Hong Kong, Hong Kong", 252 268 flag: "๐Ÿ‡ญ๐Ÿ‡ฐ", 253 269 continent: "Asia", 270 + provider: "fly", 254 271 }, 255 272 iad: { 256 273 code: "iad", 257 274 location: "Ashburn, Virginia, USA", 258 275 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 259 276 continent: "North America", 277 + provider: "fly", 260 278 }, 261 279 jnb: { 262 280 code: "jnb", 263 281 location: "Johannesburg, South Africa", 264 282 flag: "๐Ÿ‡ฟ๐Ÿ‡ฆ", 265 283 continent: "Africa", 284 + provider: "fly", 266 285 }, 267 286 lax: { 268 287 code: "lax", 269 288 location: "Los Angeles, California, USA", 270 289 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 271 290 continent: "North America", 291 + provider: "fly", 272 292 }, 273 293 lhr: { 274 294 code: "lhr", 275 295 location: "London, United Kingdom", 276 296 flag: "๐Ÿ‡ฌ๐Ÿ‡ง", 277 297 continent: "Europe", 298 + provider: "fly", 278 299 }, 279 300 mad: { 280 301 code: "mad", 281 302 location: "Madrid, Spain", 282 303 flag: "๐Ÿ‡ช๐Ÿ‡ธ", 283 304 continent: "Europe", 305 + provider: "fly", 284 306 }, 285 307 mia: { 286 308 code: "mia", 287 309 location: "Miami, Florida, USA", 288 310 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 289 311 continent: "North America", 312 + provider: "fly", 290 313 }, 291 314 nrt: { 292 315 code: "nrt", 293 316 location: "Tokyo, Japan", 294 317 flag: "๐Ÿ‡ฏ๐Ÿ‡ต", 295 318 continent: "Asia", 319 + provider: "fly", 296 320 }, 297 321 ord: { 298 322 code: "ord", 299 323 location: "Chicago, Illinois, USA", 300 324 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 301 325 continent: "North America", 326 + provider: "fly", 302 327 }, 303 328 otp: { 304 329 code: "otp", 305 330 location: "Bucharest, Romania", 306 331 flag: "๐Ÿ‡ท๐Ÿ‡ด", 307 332 continent: "Europe", 333 + provider: "fly", 308 334 }, 309 335 phx: { 310 336 code: "phx", 311 337 location: "Phoenix, Arizona, USA", 312 338 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 313 339 continent: "North America", 340 + provider: "fly", 314 341 }, 315 342 qro: { 316 343 code: "qro", 317 344 location: "Querรฉtaro, Mexico", 318 345 flag: "๐Ÿ‡ฒ๐Ÿ‡ฝ", 319 346 continent: "North America", 347 + provider: "fly", 320 348 }, 321 349 scl: { 322 350 code: "scl", 323 351 location: "Santiago, Chile", 324 352 flag: "๐Ÿ‡จ๐Ÿ‡ฑ", 325 353 continent: "South America", 354 + provider: "fly", 326 355 }, 327 356 sjc: { 328 357 code: "sjc", 329 358 location: "San Jose, California, USA", 330 359 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 331 360 continent: "North America", 361 + provider: "fly", 332 362 }, 333 363 sea: { 334 364 code: "sea", 335 365 location: "Seattle, Washington, USA", 336 366 flag: "๐Ÿ‡บ๐Ÿ‡ธ", 337 367 continent: "North America", 368 + provider: "fly", 338 369 }, 339 370 sin: { 340 371 code: "sin", 341 372 location: "Singapore, Singapore", 342 373 flag: "๐Ÿ‡ธ๐Ÿ‡ฌ", 343 374 continent: "Asia", 375 + provider: "fly", 344 376 }, 345 377 syd: { 346 378 code: "syd", 347 379 location: "Sydney, Australia", 348 380 flag: "๐Ÿ‡ฆ๐Ÿ‡บ", 349 381 continent: "Oceania", 382 + provider: "fly", 350 383 }, 351 384 waw: { 352 385 code: "waw", 353 386 location: "Warsaw, Poland", 354 387 flag: "๐Ÿ‡ต๐Ÿ‡ฑ", 355 388 continent: "Europe", 389 + provider: "fly", 356 390 }, 357 391 yul: { 358 392 code: "yul", 359 393 location: "Montreal, Canada", 360 394 flag: "๐Ÿ‡จ๐Ÿ‡ฆ", 361 395 continent: "North America", 396 + provider: "fly", 362 397 }, 363 398 yyz: { 364 399 code: "yyz", 365 400 location: "Toronto, Canada", 366 401 flag: "๐Ÿ‡จ๐Ÿ‡ฆ", 367 402 continent: "North America", 403 + provider: "fly", 404 + }, 405 + koyeb_fra: { 406 + code: "koyeb_fra", 407 + location: "Frankfurt, Germany", 408 + flag: "๐Ÿ‡ฉ๐Ÿ‡ช", 409 + continent: "Europe", 410 + provider: "koyeb", 411 + }, 412 + koyeb_par: { 413 + code: "koyeb_par", 414 + location: "Paris, France", 415 + flag: "๐Ÿ‡ซ๐Ÿ‡ท", 416 + continent: "Europe", 417 + provider: "koyeb", 418 + }, 419 + koyeb_sfo: { 420 + code: "koyeb_sfo", 421 + location: "San Francisco, USA", 422 + flag: "๐Ÿ‡บ๐Ÿ‡ธ", 423 + continent: "North America", 424 + provider: "koyeb", 425 + }, 426 + koyeb_sin: { 427 + code: "koyeb_sin", 428 + location: "Singapore, Singapore", 429 + flag: "๐Ÿ‡ธ๐Ÿ‡ฌ", 430 + continent: "Asia", 431 + provider: "koyeb", 432 + }, 433 + koyeb_tyo: { 434 + code: "koyeb_tyo", 435 + location: "Tokyo, Japan", 436 + flag: "๐Ÿ‡ฏ๐Ÿ‡ต", 437 + continent: "Asia", 438 + provider: "koyeb", 439 + }, 440 + koyeb_was: { 441 + code: "koyeb_was", 442 + location: "Washington, USA", 443 + flag: "๐Ÿ‡บ๐Ÿ‡ธ", 444 + continent: "North America", 445 + provider: "koyeb", 368 446 }, 369 447 } as const; 370 448 371 449 // const r = t.flatMap((u) => u[1].continent); 372 450 373 - export const groupByContinent = Object.entries(flyRegionsDict).reduce< 374 - Record< 375 - | "Europe" 376 - | "North America" 377 - | "South America" 378 - | "Asia" 379 - | "Africa" 380 - | "Oceania", 381 - { 382 - code: MonitorFlyRegion; 383 - location: string; 384 - flag: string; 385 - continent: 386 - | "Europe" 387 - | "North America" 388 - | "South America" 389 - | "Asia" 390 - | "Africa" 391 - | "Oceania"; 392 - }[] 393 - > 451 + export const groupByContinent = Object.entries(regionDict).reduce< 452 + Record<Continent, RegionInfo[]> 394 453 >( 395 454 (acc, [_key, value]) => { 396 455 Object.assign(acc, {
+12 -9
pnpm-lock.yaml
··· 187 187 version: 1.2.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) 188 188 '@sentry/nextjs': 189 189 specifier: 8.46.0 190 - version: 8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1) 190 + version: 8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1) 191 191 '@stripe/stripe-js': 192 192 specifier: 2.1.6 193 193 version: 2.1.6 ··· 202 202 version: 11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2) 203 203 '@trpc/next': 204 204 specifier: 11.4.4 205 - version: 11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/react-query@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(next@15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2) 205 + version: 11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/react-query@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2) 206 206 '@trpc/react-query': 207 207 specifier: 11.4.4 208 208 version: 11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2) ··· 650 650 version: 1.2.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) 651 651 '@sentry/nextjs': 652 652 specifier: 8.46.0 653 - version: 8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1) 653 + version: 8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1) 654 654 '@stripe/stripe-js': 655 655 specifier: 2.1.6 656 656 version: 2.1.6 ··· 665 665 version: 11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2) 666 666 '@trpc/next': 667 667 specifier: 11.4.4 668 - version: 11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/react-query@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(next@15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2) 668 + version: 11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/react-query@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2) 669 669 '@trpc/react-query': 670 670 specifier: 11.4.4 671 671 version: 11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2) ··· 826 826 '@openstatus/header-analysis': 827 827 specifier: workspace:* 828 828 version: link:../../packages/header-analysis 829 + '@openstatus/icons': 830 + specifier: workspace:* 831 + version: link:../../packages/icons 829 832 '@openstatus/notification-discord': 830 833 specifier: workspace:* 831 834 version: link:../../packages/notifications/discord ··· 16336 16339 '@sentry/types': 8.9.2 16337 16340 '@sentry/utils': 8.9.2 16338 16341 16339 - '@sentry/nextjs@8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1)': 16342 + '@sentry/nextjs@8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1(esbuild@0.21.5))': 16340 16343 dependencies: 16341 16344 '@opentelemetry/api': 1.9.0 16342 16345 '@opentelemetry/semantic-conventions': 1.28.0 ··· 16347 16350 '@sentry/opentelemetry': 8.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0) 16348 16351 '@sentry/react': 8.46.0(react@19.1.1) 16349 16352 '@sentry/vercel-edge': 8.46.0 16350 - '@sentry/webpack-plugin': 2.22.7(encoding@0.1.13)(webpack@5.97.1) 16353 + '@sentry/webpack-plugin': 2.22.7(encoding@0.1.13)(webpack@5.97.1(esbuild@0.21.5)) 16351 16354 chalk: 3.0.0 16352 16355 next: 15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) 16353 16356 resolve: 1.22.8 ··· 16362 16365 - supports-color 16363 16366 - webpack 16364 16367 16365 - '@sentry/nextjs@8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1(esbuild@0.21.5))': 16368 + '@sentry/nextjs@8.46.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(webpack@5.97.1)': 16366 16369 dependencies: 16367 16370 '@opentelemetry/api': 1.9.0 16368 16371 '@opentelemetry/semantic-conventions': 1.28.0 ··· 16373 16376 '@sentry/opentelemetry': 8.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0) 16374 16377 '@sentry/react': 8.46.0(react@19.1.1) 16375 16378 '@sentry/vercel-edge': 8.46.0 16376 - '@sentry/webpack-plugin': 2.22.7(encoding@0.1.13)(webpack@5.97.1(esbuild@0.21.5)) 16379 + '@sentry/webpack-plugin': 2.22.7(encoding@0.1.13)(webpack@5.97.1) 16377 16380 chalk: 3.0.0 16378 16381 next: 15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) 16379 16382 resolve: 1.22.8 ··· 17126 17129 '@tanstack/react-query': 5.80.7(react@19.1.1) 17127 17130 '@trpc/react-query': 11.4.4(@tanstack/react-query@5.80.7(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2) 17128 17131 17129 - '@trpc/next@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/react-query@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(next@15.5.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2)': 17132 + '@trpc/next@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/react-query@11.4.4(@tanstack/react-query@5.81.5(react@19.1.1))(@trpc/client@11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2))(@trpc/server@11.4.4(typescript@5.7.2))(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.7.2)': 17130 17133 dependencies: 17131 17134 '@trpc/client': 11.4.4(@trpc/server@11.4.4(typescript@5.7.2))(typescript@5.7.2) 17132 17135 '@trpc/server': 11.4.4(typescript@5.7.2)