From 842b06fa388ed1d392be7ce5dbab9cd00a140671 Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Tue, 27 Jun 2023 22:20:41 +0200 Subject: [PATCH] Feat/cron ping self monitor (#5) * chore: create pipes and datasource * chore: update cron and ping * fix: metadata * fix: cronjob * wip: small fixes and updates * fix: monitor position * chore: extend tiny schema and improve cron * chore: small adjustments --- .gitignore | 3 + apps/web/package.json | 3 + apps/web/src/app/_components/background.tsx | 2 +- .../src/app/_components/status-container.tsx | 41 ++ apps/web/src/app/_mocks/response-list.json | 23 + apps/web/src/app/api/og/route.tsx | 5 +- apps/web/src/app/api/v0/cron/route.ts | 48 ++ apps/web/src/app/api/v0/ping/route.ts | 17 + apps/web/src/app/layout.tsx | 16 +- apps/web/src/app/page.tsx | 25 +- apps/web/src/hooks/use-mouse-move.tsx | 2 + apps/web/src/middleware.ts | 2 +- apps/web/vercel.json | 8 + packages/tinybird/package.json | 24 + packages/tinybird/src/client.ts | 43 ++ packages/tinybird/src/index.ts | 2 + packages/tinybird/tsconfig.json | 7 + packages/tinybird/tsup.config.ts | 11 + packages/upstash/package.json | 26 + packages/upstash/src/index.ts | 2 + packages/upstash/src/redis/client.ts | 4 + packages/upstash/tsconfig.json | 7 + packages/upstash/tsup.config.ts | 11 + pnpm-lock.yaml | 644 +++++++++++++++--- turbo.json | 4 +- 25 files changed, 855 insertions(+), 125 deletions(-) create mode 100644 apps/web/src/app/_components/status-container.tsx create mode 100644 apps/web/src/app/_mocks/response-list.json create mode 100644 apps/web/src/app/api/v0/cron/route.ts create mode 100644 apps/web/src/app/api/v0/ping/route.ts create mode 100644 apps/web/vercel.json create mode 100644 packages/tinybird/package.json create mode 100644 packages/tinybird/src/client.ts create mode 100644 packages/tinybird/src/index.ts create mode 100644 packages/tinybird/tsconfig.json create mode 100644 packages/tinybird/tsup.config.ts create mode 100644 packages/upstash/package.json create mode 100644 packages/upstash/src/index.ts create mode 100644 packages/upstash/src/redis/client.ts create mode 100644 packages/upstash/tsconfig.json create mode 100644 packages/upstash/tsup.config.ts diff --git a/.gitignore b/.gitignore index d1595af42..e1a58eddd 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # vercel .vercel + +# packages +dist diff --git a/apps/web/package.json b/apps/web/package.json index 64d37a401..6c3bb9515 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,12 +11,15 @@ "dependencies": { "@clerk/nextjs": "^4.21.3", "@openstatus/db": "workspace:^", + "@openstatus/tinybird": "workspace:*", + "@openstatus/upstash": "workspace:*", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.4", "@t3-oss/env-nextjs": "0.4.1", "@upstash/redis": "^1.21.0", "class-variance-authority": "^0.6.0", "clsx": "^1.2.1", + "date-fns": "^2.30.0", "lucide-react": "^0.244.0", "next": "13.4.6", "next-plausible": "3.7.2", diff --git a/apps/web/src/app/_components/background.tsx b/apps/web/src/app/_components/background.tsx index b43aa0dfa..fbd9d7117 100644 --- a/apps/web/src/app/_components/background.tsx +++ b/apps/web/src/app/_components/background.tsx @@ -11,7 +11,7 @@ export default function Background({ useMouseMove(); return ( <> -
+
diff --git a/apps/web/src/app/_components/status-container.tsx b/apps/web/src/app/_components/status-container.tsx new file mode 100644 index 000000000..3e8fbb2f3 --- /dev/null +++ b/apps/web/src/app/_components/status-container.tsx @@ -0,0 +1,41 @@ +import { Badge } from "@/components/ui/badge"; +import { cn } from "@/lib/utils"; +import { formatDistance } from "date-fns"; + +// TODO: consistency in wording +export function StatusContainer({ + events, +}: { + // FIXME: should be return type + events: { id: string; timestamp: number; statusCode: number }[]; +}) { + return ( + // OVERFLOW-HIDDEN +
+
+
    + {events.map(({ timestamp, statusCode }) => { + return ( +
  • +

    + {formatDistance(new Date(timestamp), new Date(), { + addSuffix: true, + includeSeconds: true, + })} +

    + + {statusCode} +
    + +
  • + ); + })} +
+
+ ); +} diff --git a/apps/web/src/app/_mocks/response-list.json b/apps/web/src/app/_mocks/response-list.json new file mode 100644 index 000000000..ec62546ea --- /dev/null +++ b/apps/web/src/app/_mocks/response-list.json @@ -0,0 +1,23 @@ +[ + { + "id": "openstatus", + "timestamp": 1687718354031, + "statusCode": 200, + "latency": 221, + "url": "http://localhost:3000/api/v0/ping" + }, + { + "id": "openstatus", + "timestamp": 1687718326838, + "statusCode": 500, + "latency": 660, + "url": "http://localhost:3000/api/v0/ping" + }, + { + "id": "openstatus", + "timestamp": 1687718289489, + "statusCode": 500, + "latency": 332, + "url": "http://localhost:3000/api/v0/ping" + } +] diff --git a/apps/web/src/app/api/og/route.tsx b/apps/web/src/app/api/og/route.tsx index f6f8dd8ee..fae313d41 100644 --- a/apps/web/src/app/api/og/route.tsx +++ b/apps/web/src/app/api/og/route.tsx @@ -35,9 +35,8 @@ export async function GET(req: Request) { tw="flex w-full h-full absolute inset-0" // not every css variable is supported style={{ - backgroundImage: "radial-gradient(#94a3b8 10%, transparent 10%)", - backgroundPosition: "0px 0px, 6px 6px", - backgroundSize: "12px 12px", + backgroundImage: "radial-gradient(#cbd5e1 10%, transparent 10%)", + backgroundSize: "32px 32px", filter: "blur(1px)", // to be discussed... couldn't put it inside the content container }} >
diff --git a/apps/web/src/app/api/v0/cron/route.ts b/apps/web/src/app/api/v0/cron/route.ts new file mode 100644 index 000000000..d1f731af1 --- /dev/null +++ b/apps/web/src/app/api/v0/cron/route.ts @@ -0,0 +1,48 @@ +import { env } from "@/env.mjs"; +import { Tinybird, publishPingResponse } from "@openstatus/tinybird"; + +// TODO: create one route per region +export const preferredRegion = ["fra1"]; +export const runtime = "edge"; + +const DEFAULT_URL = + process.env.NODE_ENV === "development" + ? "http://localhost:3000" + : process.env.VERCEL_URL; + +const tb = new Tinybird({ token: env.TINY_BIRD_API_KEY }); + +// TODO: create a package for monitor +async function monitor( + res: Response, + { latency, url }: { latency: number; url: string } +) { + return await publishPingResponse(tb)({ + id: "openstatus", + timestamp: Date.now(), + statusCode: res.status, + latency, + url, + // TODO: add region, res + }); +} + +// TODO: auth middleware for user API check (unkey) +export async function GET(req: Request) { + try { + const { searchParams } = new URL(req.url); + const hasUrl = searchParams.has("url"); + const url = hasUrl ? searchParams.get("url") : `${DEFAULT_URL}/api/v0/ping`; + + const startTime = Date.now(); + const res = await fetch(url, { cache: "no-store" }); + const endTime = Date.now(); + const latency = endTime - startTime; + + await monitor(res, { latency, url }); + + return new Response("OK", { status: 200 }); + } catch (e) { + return new Response("Error", { status: 500 }); + } +} diff --git a/apps/web/src/app/api/v0/ping/route.ts b/apps/web/src/app/api/v0/ping/route.ts new file mode 100644 index 000000000..acde0f849 --- /dev/null +++ b/apps/web/src/app/api/v0/ping/route.ts @@ -0,0 +1,17 @@ +import { Redis } from "@openstatus/upstash"; + +const redis = Redis.fromEnv(); + +export async function GET(req: Request) { + const RANDOM = Math.random() > 0.9; + try { + // TODO: connect tinybird, upstash and planetscale + await redis.ping(); + if (RANDOM) { + throw new Error("Arg"); + } + return new Response("OK", { status: 200 }); + } catch { + return new Response("Error", { status: 500 }); + } +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index a8d861a77..0571c7431 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -14,21 +14,25 @@ const calSans = LocalFont({ variable: "--font-calsans", }); +const TITLE = "OpenStatus"; +const DESCRIPTION = + "Open-Source alternative to your current monitoring service with beautiful status page"; + export const metadata: Metadata = { - title: "openstatus.dev", - description: "An Open Source Alternative for your next Status Page.", + title: TITLE, + description: DESCRIPTION, metadataBase: new URL("https://openstatus.dev"), twitter: { images: [`/api/og`], card: "summary_large_image", - title: "openstatus.dev", - description: "An Open Source Alternative for your next Status Page.", + title: TITLE, + description: DESCRIPTION, }, openGraph: { type: "website", images: [`/api/og`], - title: "openstatus.dev", - description: "An Open Source Alternative for your next Status Page.", + title: TITLE, + description: DESCRIPTION, }, }; diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 92881cc39..56c7aa5fb 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,18 +1,22 @@ import { Badge } from "@/components/ui/badge"; - -import { Metadata } from "next"; import { HeroForm } from "./_components/hero-form"; +import { Tinybird, getResponseList } from "@openstatus/tinybird"; +import { env } from "@/env.mjs"; +import { StatusContainer } from "./_components/status-container"; +import MOCK from "@/app/_mocks/response-list.json"; -export const metadata: Metadata = { - title: "Open-source monitoring service", - description: - "OpenStatus is an open source alternative to your current monitoring service with beautiful status page", -}; +const tb = new Tinybird({ token: env.TINY_BIRD_API_KEY }); -export default function Page() { +export default async function Page() { + // REMINDER: to be removed + let data = MOCK; + if (process.env.NODE_ENV !== "development") { + const res = await getResponseList(tb)({}); + data = res.data; + } return (
-
+
Coming Soon @@ -26,6 +30,9 @@ export default function Page() {
+
+ +