Openstatus www.openstatus.dev

🧹 some improvments (#591)

authored by

Thibault Le Ouay and committed by
GitHub
4ebf94fc 5fde5a0a

+66 -534
+23 -12
apps/server/src/checker/index.ts
··· 27 27 message: z.string().optional(), 28 28 statusCode: z.number().optional(), 29 29 region: z.enum(flyRegions), 30 - cronTimestamp: z.number().optional(), 30 + cronTimestamp: z.number(), 31 31 // status: z.enum(["active", "error"]), 32 32 }); 33 33 ··· 96 96 97 97 const monitor = selectMonitorSchema.parse(currentMonitor); 98 98 99 - if (!cronTimestamp) { 100 - console.log("cronTimestamp is undefined"); 101 - } 102 - 103 99 const numberOfRegions = monitor.regions.length; 104 100 105 101 // If the number of affected regions is greater than half of the total region, we trigger the alerting 106 102 // 4 of 6 monitor need to fail to trigger an alerting 107 103 if (nbAffectedRegion > numberOfRegions / 2) { 108 - await triggerAlerting({ monitorId, statusCode, message, region }); 109 - // create the incident and trigger the alerting 110 - await db.insert(incidentTable).values({ 111 - monitorId: Number(monitorId), 112 - workspaceId: monitor.workspaceId, 113 - startedAt: new Date(), 114 - }); 104 + // let's refetch the incident to avoid race condition 105 + const incident = await db 106 + .select() 107 + .from(incidentTable) 108 + .where( 109 + and( 110 + eq(incidentTable.monitorId, Number(monitorId)), 111 + isNull(incidentTable.resolvedAt), 112 + isNull(incidentTable.acknowledgedAt), 113 + eq(incidentTable.startedAt, new Date(cronTimestamp)), 114 + ), 115 + ) 116 + .get(); 117 + if (incident === undefined) { 118 + await db.insert(incidentTable).values({ 119 + monitorId: Number(monitorId), 120 + workspaceId: monitor.workspaceId, 121 + startedAt: new Date(cronTimestamp), 122 + }); 123 + 124 + await triggerAlerting({ monitorId, statusCode, message, region }); 125 + } 115 126 } 116 127 } 117 128 }
+7 -2
apps/server/src/v1/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 import { logger } from "hono/logger"; 3 3 4 - import type { Plan } from "@openstatus/plans"; 4 + import type { Limits } from "@openstatus/plans/src/types"; 5 5 6 6 import { middleware } from "./middleware"; 7 7 import { monitorApi } from "./monitor"; ··· 10 10 11 11 export type Variables = { 12 12 workspaceId: string; 13 - workspacePlan: Plan; 13 + workspacePlan: { 14 + title: string; 15 + description: string; 16 + price: number; 17 + limits: Limits; 18 + }; 14 19 }; 15 20 16 21 export const api = new OpenAPIHono<{ Variables: Variables }>();
+2 -2
apps/server/src/v1/middleware.ts
··· 1 1 import { verifyKey } from "@unkey/api"; 2 - import type { Context, Env, Next } from "hono"; 2 + import type { Context, Next } from "hono"; 3 3 4 4 import type { Variables } from "./index"; 5 5 import { getLimitByWorkspaceId } from "./utils"; 6 6 7 7 export async function middleware( 8 - c: Context<{ Variables: Variables }, "/*", {}>, 8 + c: Context<{ Variables: Variables }, "/*">, 9 9 next: Next, 10 10 ) { 11 11 const key = c.req.header("x-openstatus-key");
+4 -7
apps/server/src/v1/monitor.ts
··· 54 54 (val) => { 55 55 if (String(val).length > 0) { 56 56 return String(val).split(","); 57 - } else { 58 - return []; 59 57 } 58 + return []; 60 59 }, 61 60 z.array(z.enum(flyRegions)), 62 61 ) ··· 94 93 (val) => { 95 94 if (String(val).length > 0) { 96 95 return JSON.parse(String(val)); 97 - } else { 98 - return []; 99 96 } 97 + return []; 100 98 }, 101 99 z.array(z.object({ key: z.string(), value: z.string() })).default([]), 102 100 ) ··· 147 145 headers: z 148 146 .preprocess( 149 147 (val) => { 150 - if (!!val) { 148 + if (val) { 151 149 return val; 152 - } else { 153 - return []; 154 150 } 151 + return []; 155 152 }, 156 153 z.array(z.object({ key: z.string(), value: z.string() })).default([]), 157 154 )
-1
apps/web/src/app/(content)/changelog/page.tsx
··· 1 1 import type { Metadata } from "next"; 2 - import Link from "next/link"; 3 2 import { allChangelogs } from "contentlayer/generated"; 4 3 5 4 import {
+2 -1
apps/web/src/app/_components/table-input-container.tsx
··· 16 16 .filter((event) => { 17 17 if (search?.status && event.statusCode !== Number(search.status)) { 18 18 return false; 19 - } else if (search?.region && event.region !== search.region) { 19 + } 20 + if (search?.region && event.region !== search.region) { 20 21 return false; 21 22 } 22 23 return true;
-8
apps/web/src/app/api/checker/regions/_checker.ts
··· 1 1 import { Receiver } from "@upstash/qstash"; 2 - import { nanoid } from "nanoid"; 3 2 4 3 import { db, eq, schema } from "@openstatus/db"; 5 4 import type { MonitorStatus } from "@openstatus/db/src/schema"; 6 5 import { 7 - selectMonitorSchema, 8 - selectNotificationSchema, 9 - } from "@openstatus/db/src/schema"; 10 - import { 11 6 publishPingResponse, 12 7 tbIngestPingResponse, 13 - Tinybird, 14 8 } from "@openstatus/tinybird"; 15 9 16 10 import { env } from "@/env"; ··· 21 15 url: true, 22 16 cronTimestamp: true, 23 17 }); 24 - 25 - const tb = new Tinybird({ token: env.TINY_BIRD_API_KEY }); 26 18 27 19 const monitor = async ( 28 20 payload: Payload,
-12
apps/web/src/app/api/checker/regions/arn1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["arn1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-17
apps/web/src/app/api/checker/regions/auto/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = "auto"; 7 - export const dynamic = "force-dynamic"; 8 - // Fix is a random region let's figure where does vercel push it 9 - 10 - export async function POST(request: Request) { 11 - const region = process.env.VERCEL_REGION; 12 - if (!region) { 13 - throw new Error("No region"); 14 - } 15 - await checker(request, region); 16 - return NextResponse.json({ success: true }); 17 - }
-12
apps/web/src/app/api/checker/regions/bom1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["bom1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/cdg1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["cdg1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/cle1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["cle1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/cpt1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["cpt1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/dub1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["dub1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/fra1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["fra1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/gru1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["gru1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/hkg1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["hkg1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/hnd1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["hnd1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/iad1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["iad1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/icn1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["icn1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/kix1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["kix1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/lhr1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["lhr1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/pdx1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["pdx1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/sfo1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["sfo1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/sin1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["sin1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
-12
apps/web/src/app/api/checker/regions/syd1/route.ts
··· 1 - import { NextResponse } from "next/server"; 2 - 3 - import { checker } from "../_checker"; 4 - 5 - export const runtime = "edge"; 6 - export const preferredRegion = ["syd1"]; 7 - export const dynamic = "force-dynamic"; 8 - 9 - export async function POST(request: Request) { 10 - await checker(request, preferredRegion[0]); 11 - return NextResponse.json({ success: true }); 12 - }
+1
apps/web/src/app/api/checker/test/route.ts
··· 19 19 return NextResponse.json({ success: false }, { status: 400 }); 20 20 } 21 21 22 + // TODO: let's use our own go checker to check the endpoint 22 23 const check = await ping(_valid.data); 23 24 24 25 console.log(check.status);
-22
apps/web/src/app/api/checker/utils.ts
··· 1 - import type { 2 - Monitor, 3 - Notification, 4 - NotificationProvider, 5 - } from "@openstatus/db/src/schema"; 6 - import { sendDiscordMessage } from "@openstatus/notification-discord"; 7 - import { send as sendEmail } from "@openstatus/notification-emails"; 8 - import { sendSlackMessage } from "@openstatus/notification-slack"; 9 - 10 - // type sendNotificationType = ({ 11 - // monitor, 12 - // notification, 13 - // }: { 14 - // monitor: Monitor; 15 - // notification: Notification; 16 - // }) => Promise<void>; 17 - 18 - // export const providerToFunction = { 19 - // email: sendEmail, 20 - // slack: sendSlackMessage, 21 - // discord: sendDiscordMessage, 22 - // } satisfies Record<NotificationProvider, sendNotificationType>;
-1
apps/web/src/app/api/integrations/vercel/callback/route.ts
··· 1 - export { GET } from "@openstatus/vercel/src/routes/callback";
-3
apps/web/src/app/api/integrations/vercel/configure/page.ts
··· 1 - import { Configure } from "@openstatus/vercel"; 2 - 3 - export default Configure;
-1
apps/web/src/app/api/integrations/vercel/route.ts
··· 1 - export { config, POST } from "@openstatus/vercel/src/routes/webhook";
+2 -2
apps/web/src/app/api/og/post/route.tsx
··· 54 54 backgroundImage: "radial-gradient(#cbd5e1 10%, transparent 10%)", 55 55 backgroundSize: "24px 24px", 56 56 }} 57 - ></div> 57 + /> 58 58 <div 59 59 tw="flex w-full h-full absolute inset-0 opacity-70" 60 60 style={{ ··· 62 62 backgroundImage: 63 63 "radial-gradient(farthest-corner at 100px 100px, #cbd5e1, white 80%)", // tbd: switch color position 64 64 }} 65 - ></div> 65 + /> 66 66 <div tw="flex flex-col h-full justify-between px-24"> 67 67 <div tw="flex flex-col flex-1 justify-end"> 68 68 <div tw="flex flex-col px-12">
+4 -7
apps/web/src/app/api/og/route.tsx
··· 68 68 backgroundImage: "radial-gradient(#cbd5e1 10%, transparent 10%)", 69 69 backgroundSize: "24px 24px", 70 70 }} 71 - ></div> 71 + /> 72 72 <div 73 73 tw="flex w-full h-full absolute inset-0 opacity-70" 74 74 style={{ ··· 76 76 backgroundImage: 77 77 "radial-gradient(farthest-corner at 100px 100px, #cbd5e1, white 80%)", // tbd: switch color position 78 78 }} 79 - ></div> 79 + /> 80 80 <div tw="max-w-4xl relative flex flex-col"> 81 81 <h1 style={{ fontFamily: "Cal" }} tw="text-6xl"> 82 82 {title} ··· 92 92 {/* Empty State */} 93 93 {new Array(data.length).fill(null).map((_, i) => { 94 94 return ( 95 - <div 96 - key={i} 97 - tw="h-16 w-3 rounded-full mr-1 bg-black/20" 98 - ></div> 95 + <div key={i} tw="h-16 w-3 rounded-full mr-1 bg-black/20" /> 99 96 ); 100 97 })} 101 98 <div tw="flex flex-row-reverse absolute right-0"> ··· 118 115 "bg-red-500": variant === "down", 119 116 "bg-yellow-500": variant === "degraded", 120 117 })} 121 - ></div> 118 + /> 122 119 ); 123 120 })} 124 121 </div>
-18
apps/web/src/app/app/[workspaceSlug]/(dashboard)/integrations/loading.tsx
··· 1 - import { Skeleton } from "@openstatus/ui"; 2 - 3 - import { Header } from "@/components/dashboard/header"; 4 - 5 - export default function Loading() { 6 - return ( 7 - <div className="grid gap-6 md:grid-cols-1 md:gap-8"> 8 - <div className="col-span-full flex w-full justify-between"> 9 - <Header.Skeleton> 10 - <Skeleton className="h-9 w-20" /> 11 - </Header.Skeleton> 12 - </div> 13 - <Skeleton className="h-4 w-24" /> 14 - <Skeleton className="h-48 w-full" /> 15 - <Skeleton className="h-48 w-full" /> 16 - </div> 17 - ); 18 - }
-39
apps/web/src/app/app/[workspaceSlug]/(dashboard)/integrations/page.tsx
··· 1 - import * as React from "react"; 2 - 3 - import { Button } from "@openstatus/ui"; 4 - 5 - import { Container } from "@/components/dashboard/container"; 6 - import { Header } from "@/components/dashboard/header"; 7 - import { api } from "@/trpc/server"; 8 - 9 - export default async function IntegrationPage({ 10 - params, 11 - }: { 12 - params: { workspaceSlug: string }; 13 - }) { 14 - const workspace = await api.workspace.getWorkspace.query(); 15 - 16 - return ( 17 - <div className="grid gap-6 md:grid-cols-2 md:gap-8"> 18 - <Header title="Integrations" description="All our integrations" /> 19 - <Container 20 - title="Vercel" 21 - key={"vercel"} 22 - description="Connect your Vercel Project get insights." 23 - actions={[ 24 - <a 25 - href={ 26 - workspace?.id === 1 27 - ? "https://vercel.com/integrations/openstatus/new" 28 - : "#" 29 - } 30 - target="_blank" 31 - key={"vercel"} 32 - > 33 - <Button>{workspace?.id === 1 ? "Configure" : "Coming soon"}</Button> 34 - </a>, 35 - ]} 36 - ></Container> 37 - </div> 38 - ); 39 - }
-122
apps/web/src/app/app/[workspaceSlug]/(dashboard)/integrations/vercel/configure/page.tsx
··· 1 - import { revalidatePath } from "next/cache"; 2 - import { cookies } from "next/headers"; 3 - import { redirect } from "next/navigation"; 4 - 5 - import { SubmitButton } from "@openstatus/vercel/src/components/submit-button"; 6 - import { 7 - createLogDrain, 8 - deleteLogDrain, 9 - getLogDrains, 10 - getProjects, 11 - } from "@openstatus/vercel/src/libs/client"; 12 - import { decrypt } from "@openstatus/vercel/src/libs/crypto"; 13 - 14 - import { api } from "@/trpc/server"; 15 - 16 - export default async function Configure({ 17 - params, 18 - }: { 19 - params: { workspaceSlug: string }; 20 - }) { 21 - const iv = cookies().get("iv")?.value; 22 - const encryptedToken = cookies().get("token")?.value; 23 - const teamId = cookies().get("teamId")?.value; 24 - 25 - if (!iv || !encryptedToken) { 26 - /** Redirect to access new token */ 27 - return redirect("/app"); 28 - } 29 - 30 - const token = decrypt( 31 - Buffer.from(iv || "", "base64url"), 32 - Buffer.from(encryptedToken || "", "base64url"), 33 - ).toString(); 34 - 35 - let logDrains = await getLogDrains(token, teamId); 36 - 37 - const projects = await getProjects(token, teamId); 38 - 39 - for (const project of projects.projects) { 40 - console.log({ project }); 41 - console.log("create integration"); 42 - console.log(project.id); 43 - api.integration.createIntegration.mutate({ 44 - workspaceSlug: params.workspaceSlug, 45 - input: { 46 - name: "Vercel", 47 - data: JSON.stringify(project), 48 - externalId: project.id, 49 - }, 50 - }); 51 - } 52 - // Create integration project if it doesn't exist 53 - 54 - if (logDrains.length === 0) { 55 - logDrains = [ 56 - await createLogDrain( 57 - token, 58 - // @ts-expect-error We need more data - but this is a demo 59 - { 60 - deliveryFormat: "json", 61 - name: "OpenStatus Log Drain", 62 - // TODO: update with correct url 63 - url: "https://www.openstatus.dev/api/integrations/vercel", 64 - sources: ["static", "lambda", "build", "edge", "external"], 65 - // headers: { "key": "value"} 66 - }, 67 - teamId, 68 - ), 69 - ]; 70 - } 71 - 72 - // TODO: automatically create log drain on installation 73 - // async function create(formData: FormData) { 74 - // "use server"; 75 - // await createLogDrain( 76 - // token, 77 - // // @ts-expect-error We need more data - but this is a demo 78 - // { 79 - // deliveryFormat: "json", 80 - // name: "OpenStatus Log Drain", 81 - // // TODO: update with correct url 82 - // url: "https://6be9-2a0d-3344-2324-1e04-4dc7-d06a-a389-48c0.ngrok-free.app/api/integrations/vercel", 83 - // sources: ["static", "lambda", "edge", "external"], 84 - // // headers: { "key": "value"} 85 - // }, 86 - // teamId, 87 - // ); 88 - // revalidatePath("/"); 89 - // } 90 - 91 - async function _delete(formData: FormData) { 92 - "use server"; 93 - const id = formData.get("id")?.toString(); 94 - console.log({ id }); 95 - if (id) { 96 - await deleteLogDrain(token, id, String(teamId)); 97 - revalidatePath("/"); 98 - } 99 - } 100 - 101 - return ( 102 - <div className="flex w-full flex-col items-center justify-center"> 103 - <div className="border-border m-3 grid w-full max-w-xl gap-3 rounded-lg border p-6 backdrop-blur-[2px]"> 104 - <h1 className="font-cal text-2xl">Configure Vercel Integration</h1> 105 - <ul> 106 - {logDrains.map((item) => ( 107 - <li 108 - key={item.id} 109 - className="flex items-center justify-between gap-2" 110 - > 111 - <p>{item.name}</p> 112 - <form action={_delete}> 113 - <input name="id" value={item.id} className="hidden" /> 114 - <SubmitButton>Remove integration</SubmitButton> 115 - </form> 116 - </li> 117 - ))} 118 - </ul> 119 - </div> 120 - </div> 121 - ); 122 - }
+1 -5
apps/web/src/app/app/[workspaceSlug]/(dashboard)/notifications/page.tsx
··· 10 10 import { api } from "@/trpc/server"; 11 11 import { EmptyState } from "./_components/empty-state"; 12 12 13 - export default async function NotificationPage({ 14 - params, 15 - }: { 16 - params: { workspaceSlug: string }; 17 - }) { 13 + export default async function NotificationPage() { 18 14 const notifications = 19 15 await api.notification.getNotificationsByWorkspace.query(); 20 16 const isLimitReached =
-1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/billing/page.tsx
··· 1 1 import { api } from "@/trpc/server"; 2 - import { CustomerPortalButton } from "./_components/customer-portal-button"; 3 2 import { SettingsPlan } from "./_components/plan"; 4 3 5 4 export default async function BillingPage() {
-1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/status-reports/(overview)/layout.tsx
··· 5 5 6 6 import { Header } from "@/components/dashboard/header"; 7 7 import { HelpCallout } from "@/components/dashboard/help-callout"; 8 - import { api } from "@/trpc/server"; 9 8 10 9 export default async function Layout({ 11 10 children,
+1 -5
apps/web/src/app/app/[workspaceSlug]/(dashboard)/status-reports/new/page.tsx
··· 1 1 import { StatusReportForm } from "@/components/forms/status-report-form"; 2 2 import { api } from "@/trpc/server"; 3 3 4 - export default async function NewPage({ 5 - params, 6 - }: { 7 - params: { workspaceSlug: string }; 8 - }) { 4 + export default async function NewPage() { 9 5 const monitors = await api.monitor.getMonitorsByWorkspace.query(); 10 6 11 7 const pages = await api.page.getPagesByWorkspace.query();
-2
apps/web/src/app/play/checker/[id]/_components/response-header-table.tsx
··· 1 - import { Copy } from "lucide-react"; 2 - 3 1 import { 4 2 Table, 5 3 TableBody,
+1 -1
apps/web/src/app/play/checker/[id]/_components/response-timing-table.tsx
··· 27 27 <TableRow> 28 28 <TableHead className="w-[120px] md:w-[150px]">Timing</TableHead> 29 29 <TableHead className="w-[120px] md:w-[150px]">Duration</TableHead> 30 - <TableHead></TableHead> 30 + <TableHead /> 31 31 </TableRow> 32 32 </TableHeader> 33 33 <TableBody>
+1 -1
apps/web/src/app/play/checker/[id]/page.tsx
··· 40 40 const check = 41 41 data.checks.find((i) => i.region === selectedRegion) || data.checks?.[0]; 42 42 43 - const { status, region, headers, latency, timing } = check; 43 + const { region, headers, timing } = check; 44 44 45 45 return ( 46 46 <>
-1
apps/web/src/app/play/checker/_components/checker-form.tsx
··· 10 10 Button, 11 11 Form, 12 12 FormControl, 13 - FormDescription, 14 13 FormField, 15 14 FormItem, 16 15 FormLabel,
-6
apps/web/src/app/status-page/[domain]/incidents/page.tsx
··· 1 - import type { Metadata } from "next"; 2 1 import { notFound } from "next/navigation"; 3 2 4 - import { 5 - defaultMetadata, 6 - ogMetadata, 7 - twitterMetadata, 8 - } from "@/app/shared-metadata"; 9 3 import { Header } from "@/components/dashboard/header"; 10 4 import { IncidentList } from "@/components/status-page/incident-list"; 11 5 import { api } from "@/trpc/server";
+1 -12
apps/web/src/components/data-table/incident/data-table-row-actions.tsx
··· 1 1 "use client"; 2 2 3 3 import * as React from "react"; 4 - import Link from "next/link"; 5 4 import { useRouter } from "next/navigation"; 6 5 import type { Row } from "@tanstack/react-table"; 7 6 import { MoreHorizontal } from "lucide-react"; 8 7 9 8 import { selectIncidentSchema } from "@openstatus/db/src/schema"; 10 9 import { 11 - AlertDialog, 12 - AlertDialogAction, 13 - AlertDialogCancel, 14 - AlertDialogContent, 15 - AlertDialogDescription, 16 - AlertDialogFooter, 17 - AlertDialogHeader, 18 - AlertDialogTitle, 19 - AlertDialogTrigger, 20 10 Button, 21 11 DropdownMenu, 22 12 DropdownMenuContent, ··· 25 15 DropdownMenuTrigger, 26 16 } from "@openstatus/ui"; 27 17 28 - import { LoadingAnimation } from "@/components/loading-animation"; 29 18 import { useToastAction } from "@/hooks/use-toast-action"; 30 19 import { api } from "@/trpc/client"; 31 20 ··· 39 28 const incident = selectIncidentSchema.parse(row.original); 40 29 const router = useRouter(); 41 30 const { toast } = useToastAction(); 42 - const [isPending, startTransition] = React.useTransition(); 31 + const [_isPending, startTransition] = React.useTransition(); 43 32 44 33 async function resolved() { 45 34 startTransition(async () => {
+2 -1
apps/web/src/components/data-table/monitor/data-table-row-actions.tsx
··· 76 76 async function onTest() { 77 77 startTransition(async () => { 78 78 const { url, body, method, headers } = monitor; 79 - const res = await fetch(`/api/checker/test`, { 79 + 80 + const res = await fetch("/api/checker/test", { 80 81 method: "POST", 81 82 headers: new Headers({ 82 83 "Content-Type": "application/json",
-1
apps/web/src/components/forms/workspace-form.tsx
··· 6 6 import { useForm } from "react-hook-form"; 7 7 import * as z from "zod"; 8 8 9 - import { insertWorkspaceSchema } from "@openstatus/db/src/schema"; 10 9 import { 11 10 Button, 12 11 Form,
+14 -14
apps/web/src/components/marketing/pricing/pricing-table.tsx
··· 101 101 </TableHeader> 102 102 <TableBody> 103 103 {Object.entries(pricingTableConfig).map( 104 - ([key, { label, features }], i) => { 104 + ([key, { label, features }], _i) => { 105 105 return ( 106 - <Fragment key={i}> 106 + <Fragment key={key}> 107 107 <TableRow className="bg-muted/50"> 108 108 <TableCell 109 109 colSpan={selectedPlans.length + 1} ··· 114 114 </TableRow> 115 115 {features.map(({ label, value, badge }, i) => { 116 116 return ( 117 - <TableRow key={i}> 117 + <TableRow key={key}> 118 118 <TableCell className="gap-1"> 119 119 {label}{" "} 120 120 {badge ? ( ··· 129 129 return ( 130 130 <Check className="text-foreground h-4 w-4" /> 131 131 ); 132 - } else { 133 - return ( 134 - <span className="text-muted-foreground/50"> 135 - &#8208; 136 - </span> 137 - ); 138 132 } 139 - } else if (typeof limitValue === "number") { 133 + return ( 134 + <span className="text-muted-foreground/50"> 135 + &#8208; 136 + </span> 137 + ); 138 + } 139 + if (typeof limitValue === "number") { 140 140 return ( 141 141 <span className="font-mono">{limitValue}</span> 142 142 ); 143 - } else if ( 143 + } 144 + if ( 144 145 Array.isArray(limitValue) && 145 146 limitValue.length > 0 146 147 ) { 147 148 return limitValue[0]; 148 - } else { 149 - return limitValue; 150 149 } 150 + return limitValue; 151 151 } 152 152 153 153 return ( 154 154 <TableCell 155 - key={i} 155 + key={key} 156 156 className={cn( 157 157 "p-3", 158 158 plan.key === "team" && "bg-muted/30",
-1
apps/web/src/components/marketing/stats.tsx
··· 1 1 import { Shell } from "@/components/dashboard/shell"; 2 2 import { getHomeStatsData } from "@/lib/tb"; 3 3 import { numberFormatter } from "@/lib/utils"; 4 - import { api } from "@/trpc/server"; 5 4 6 5 export async function Stats() { 7 6 const tbTotalStats = await getHomeStatsData({});
-1
apps/web/src/components/tracker.tsx
··· 153 153 const Bar = ({ 154 154 count, 155 155 ok, 156 - avgLatency, 157 156 p95Latency, 158 157 day, 159 158 blacklist,