+ Canary +
+
+ The /canary endpoint
+ responds with the status code below. Add it as a monitored site to test alerting end-to-end.
+
diff --git a/e2e/contacts.spec.ts b/e2e/contacts.spec.ts index 0a76574..6d2f100 100644 --- a/e2e/contacts.spec.ts +++ b/e2e/contacts.spec.ts @@ -27,7 +27,7 @@ test("adds an email contact and displays it in the list", async ({ page }) => { test("adds a Slack contact and displays it in the list", async ({ page }) => { await page.goto("/settings"); - await page.getByRole("combobox").selectOption("slack"); + await page.locator("form", { hasText: "Add" }).getByRole("combobox").selectOption("slack"); await page.getByPlaceholder("#channel name").fill("#alerts"); await page.getByPlaceholder("https://hooks.slack.com/services/...").fill("https://hooks.slack.com/services/T00/B00/test"); await page.getByRole("button", { name: "Add" }).click(); diff --git a/src/app/canary/route.ts b/src/app/canary/route.ts new file mode 100644 index 0000000..a361c43 --- /dev/null +++ b/src/app/canary/route.ts @@ -0,0 +1,28 @@ +import { createClient } from "@supabase/supabase-js" + +export const dynamic = "force-dynamic" + +export async function GET() { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) + + const { data } = await supabase + .from("settings") + .select("value") + .eq("key", "canary_status_code") + .single() + + const statusCode = data ? parseInt(data.value, 10) : 200 + + const html = ` +
Canary response: ${statusCode}
+` + + return new Response(html, { + status: statusCode, + headers: { "Content-Type": "text/html" }, + }) +} diff --git a/src/app/settings/actions.ts b/src/app/settings/actions.ts index 754458c..4834e89 100644 --- a/src/app/settings/actions.ts +++ b/src/app/settings/actions.ts @@ -45,3 +45,22 @@ export async function deleteContact(contactId: string) { if (error) return { error: `Failed to remove contact: ${error.message}` } revalidatePath("/settings") } + +const VALID_CANARY_CODES = [200, 301, 302, 400, 401, 403, 404, 408, 500, 502, 503, 504] + +export async function updateCanaryStatusCode(code: number) { + const supabase = await createClient() + const { + data: { user }, + } = await supabase.auth.getUser() + + if (!user) redirect("/login") + + if (!VALID_CANARY_CODES.includes(code)) return + + await supabase + .from("settings") + .upsert({ key: "canary_status_code", value: String(code), updated_at: new Date().toISOString() }) + + revalidatePath("/settings") +} diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 82fd070..284dc33 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -3,6 +3,7 @@ import { createClient } from "@/lib/supabase/server" import type { Contact, Invitation } from "@/lib/supabase/types" import { deleteContact } from "./actions" import AddContactForm from "@/components/AddContactForm" +import CanarySettings from "@/components/CanarySettings" import { sendInvitation, revokeInvitation } from "./invite-actions" import ActionForm from "@/components/ActionForm" import Link from "next/link" @@ -32,6 +33,14 @@ export default async function SettingsPage() { const typedInvitations = (invitations ?? []) as Invitation[] + const { data: canarySetting } = await supabase + .from("settings") + .select("value") + .eq("key", "canary_status_code") + .single() + + const canaryCode = canarySetting ? parseInt(canarySetting.value, 10) : 200 + return (
+ The /canary endpoint
+ responds with the status code below. Add it as a monitored site to test alerting end-to-end.
+