Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
16 kB ยท 455 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456// Single source of truth for docs section order, page order, and sidebar labels.// Labels mirror each page's `title` in title case, except: section-landing pages// keep a short label (e.g. title "Foundational Concepts" โ label "Overview"), and// Guides entries carry a "How to " prefix the page title may omit. Pageโsection// membership is mirrored by each doc's `category` frontmatter and cross-checked// at build time (see `validateDocsNav`).
export type DocsNavLeaf = { slug: string; label: string };export type DocsNavExternal = { link: string; label: string; external: true };// A chapter: a collapsible group of items, nestable to any depth. Has no slug of// its own โ its landing page (if any) is just the first child, by convention an// "Overview" leaf โ mirroring how a top-level section is rendered.export type DocsNavGroup = { label: string; collapsed?: boolean; items: DocsNavItem[];};export type DocsNavItem = DocsNavLeaf | DocsNavExternal | DocsNavGroup;
export type DocsNavSection = DocsNavGroup;
export const DOCS_SECTIONS = [ "Concepts", "Tutorials", "Guides", "SDK", "Reference", "Help",] as const;
export type DocsSection = (typeof DOCS_SECTIONS)[number];
export const docsNav: DocsNavSection[] = [ { label: "Concepts", items: [ { slug: "concept/getting-started", label: "Overview" }, { slug: "concept/uptime-monitoring", label: "Understanding Uptime Monitoring", }, { slug: "concept/probes-and-locations", label: "Probes, Locations, and Regions", }, { slug: "concept/private-locations", label: "Understanding Private Locations", }, { slug: "concept/status-reports-and-incidents", label: "Understanding Status Reports and Incidents", }, { slug: "concept/best-practices-status-page", label: "Building Trust with Status Pages", }, { slug: "concept/uptime-calculation-and-values", label: "Uptime Calculation and Shared Values", }, { slug: "concept/uptime-monitoring-as-code", label: "Understanding Monitoring as Code", }, { slug: "concept/latency-vs-response-time", label: "Understanding Latency vs Response Time", }, ], }, { label: "Tutorials", collapsed: true, items: [ { slug: "tutorial/getting-started", label: "Overview" }, { slug: "tutorial/create-your-first-monitor", label: "Create an Uptime Monitor", }, { slug: "tutorial/your-first-notification", label: "Wire Up Your First Notification", }, { slug: "tutorial/create-your-first-status-page", label: "Create a Status Page", }, { slug: "tutorial/your-first-status-report", label: "Publish Your First Status Report", }, { slug: "tutorial/get-started-with-openstatus-cli", label: "Get Started with openstatus CLI", }, { slug: "tutorial/manage-status-reports-cli", label: "Manage Status Reports from the CLI", }, ], }, { label: "Guides", collapsed: true, items: [ { slug: "guides/getting-started", label: "Overview" }, { slug: "guides/how-to-configure-status-page", label: "How to Configure Your Status Page", }, { slug: "guides/how-to-create-status-page-theme", label: "How to Create Your Own Status Page Theme", }, { slug: "guides/how-to-import-status-page", label: "How to Import a Status Page from Another Provider", }, { slug: "guides/how-to-translate-status-page", label: "How to translate your status page", }, { slug: "guides/how-to-embed-status-page-iframe", label: "How to Embed a Status Page in an Iframe", }, { slug: "guides/how-to-add-svg-status-badge", label: "How to Add a Status Badge to a GitHub README", }, { slug: "guides/how-to-use-react-widget", label: "How to Use openstatus React Widget", }, { slug: "guides/how-to-monitor-mcp-server", label: "How to Monitor an MCP Server", }, { slug: "guides/how-to-run-synthetic-test-github-action", label: "How to Run Synthetic Tests in GitHub Actions", }, { slug: "guides/how-to-export-metrics-to-otlp-endpoint", label: "How to Export Metrics to an OTLP Endpoint", }, { slug: "guides/how-to-setup-slack-agent", label: "How to Set Up the openstatus Slack Agent", }, { slug: "guides/how-to-deploy-statuspage-socials-notifier", label: "How to Auto-Post Status Updates to X and Bluesky", }, { slug: "guides/how-to-connect-openstatus-to-your-agent", label: "How to Connect openstatus to Your Coding Agent", }, { slug: "guides/how-to-manage-openstatus-with-terraform", label: "How to Manage Your openstatus Stack with Terraform", }, { slug: "guides/how-to-create-private-location", label: "How to Create a Private Location", }, { slug: "guides/how-to-deploy-probes-cloudflare-containers", label: "How to Deploy a Private Probe on Cloudflare Containers", }, { slug: "guides/how-to-set-up-saml-sso", label: "How to Set Up SAML Single Sign-On", }, { slug: "guides/self-hosting-openstatus", label: "How to Self-Host openstatus", }, { slug: "guides/self-host-status-page-only", label: "Self-Host the openstatus Status Page (Lightweight)", }, { slug: "guides/how-deploy-status-page-cf-pages", label: "How to Deploy a Status Page to Cloudflare Pages", }, ], }, { label: "SDK", collapsed: true, items: [ { label: "Node SDK", collapsed: true, items: [ { slug: "sdk/nodejs/overview", label: "Overview" }, { slug: "sdk/nodejs/getting-started", label: "Getting Started" }, { slug: "sdk/nodejs/authentication", label: "Authentication" }, { slug: "sdk/nodejs/monitor-service", label: "Monitor Service" }, { slug: "sdk/nodejs/status-page-service", label: "Status Page Service", }, { slug: "sdk/nodejs/status-report-service", label: "Status Report Service", }, { slug: "sdk/nodejs/maintenance-service", label: "Maintenance Service", }, { slug: "sdk/nodejs/notification-service", label: "Notification Service", }, { slug: "sdk/nodejs/private-location-service", label: "Private Location Service", }, { slug: "sdk/nodejs/health-service", label: "Health Service" }, { slug: "sdk/nodejs/error-handling", label: "Error Handling" }, { slug: "sdk/nodejs/typescript-tips", label: "TypeScript Tips" }, { slug: "sdk/nodejs/reference", label: "Reference" }, ], }, { slug: "sdk/php/overview", label: "PHP SDK" }, { slug: "sdk/python/overview", label: "Python SDK" }, ], }, { label: "Reference", collapsed: true, items: [ { slug: "reference/overview", label: "Overview" }, { link: "https://api.openstatus.dev/v1", label: "API Reference V1 - Deprecated", external: true, }, { link: "https://api.openstatus.dev/openapi", label: "API Reference V2", external: true, }, { slug: "reference/api-rate-limits", label: "API Rate Limits" }, { slug: "reference/cli-reference", label: "CLI Reference" }, { slug: "reference/mcp-server", label: "MCP Server" }, { slug: "reference/dns-monitor", label: "DNS Monitor Reference" }, { slug: "reference/grpc-monitor", label: "gRPC Monitor Reference" }, { slug: "reference/http-monitor", label: "HTTP Monitor Reference" }, { slug: "reference/icmp-monitor", label: "ICMP Monitor Reference" }, { slug: "reference/tcp-monitor", label: "TCP Monitor Reference" }, { slug: "reference/notification", label: "Notification Channels Reference", }, { slug: "reference/incident", label: "Incident Reference" }, { slug: "reference/location", label: "Location Reference" }, { slug: "reference/private-location", label: "Private Location Reference", }, { slug: "reference/status-page", label: "Status Page Reference" }, { slug: "reference/page-components", label: "Page Components Reference" }, { slug: "reference/status-report", label: "Status Report Reference" }, { slug: "reference/maintenance", label: "Maintenance Reference" }, { slug: "reference/subscriber", label: "Subscriber Reference" }, { slug: "reference/terraform", label: "Terraform Provider Reference" }, ], }, { label: "Help", collapsed: true, items: [{ slug: "help/support", label: "Need help?" }], },];
export function isExternalItem(item: DocsNavItem): item is DocsNavExternal { return "external" in item && item.external;}
export function isGroupItem(item: DocsNavItem): item is DocsNavGroup { return "items" in item;}
// First internal leaf slug in document order, descending into nested groups.// Used to derive a section/chapter's URL parent and landing page.export function firstLeafSlug(items: DocsNavItem[]): string | undefined { for (const item of items) { if (isExternalItem(item)) continue; if (isGroupItem(item)) { const nested = firstLeafSlug(item.items); if (nested) return nested; } else { return item.slug; } } return undefined;}
// Flattened, in-order list of internal doc slugs โ drives prev/next navigation// and the build-time validation against the content directory. Recurses into// nested chapters so every leaf is covered regardless of depth.export function flattenDocsNav(): DocsNavLeaf[] { const walk = (items: DocsNavItem[]): DocsNavLeaf[] => items.flatMap((item) => { if (isExternalItem(item)) return []; if (isGroupItem(item)) return walk(item.items); return [item]; }); return docsNav.flatMap((section) => walk(section.items));}
// The URL segment that represents a whole section, e.g. Concepts โ /docs/concept.// Each section's items share a unique first path segment.export function sectionParentSlug(section: DocsNavSection): string | undefined { return firstLeafSlug(section.items)?.split("/")[0];}
export function sectionForParentSlug( parentSlug: string,): DocsNavSection | undefined { return docsNav.find((s) => sectionParentSlug(s) === parentSlug);}
// Parent/section landing slugs (concept, tutorial, โฆ) for static generation.export function getParentSlugs(): string[] { return docsNav.map(sectionParentSlug).filter((s): s is string => Boolean(s));}
// A navigable node in the docs hierarchy (hub โ sections โ pages). `slug` is set// for internal docs/sections so descriptions can be resolved lazily; `description`// holds a node's own intro text (only the hub has one). Pure โ no fs.export type DocsNavNode = { label: string; href: string; slug?: string; description?: string; children?: DocsNavNode[];};
// The whole docs hierarchy as one rooted tree, derived from `docsNav`. Drives// path-agnostic listing: find a node by href, list its children at any depth.export function docsNavTree(): DocsNavNode { return { label: "openstatus documentation", href: "/docs", description: "Infra as code for uptime monitoring and status pages. Let your agents update them. Learn how to monitor your endpoints, create your status page, configure notifications, and drive it all from the CLI, Terraform, API, or MCP.", children: docsNav.map((section) => { const parent = sectionParentSlug(section); return { label: section.label, // Sections own a synthetic landing at the directory root (e.g. /docs/sdk). href: parent ? `/docs/${parent}` : "/docs", slug: firstLeafSlug(section.items), children: section.items.map(itemToNode), }; }), };}
// One nav item โ one tree node. A nested chapter's landing is its directory hub// (e.g. /docs/sdk/nodejs) โ a synthetic card grid, never a backing doc โ so its// pages live one level deeper (sdk/nodejs/overview, โฆ).function itemToNode(item: DocsNavItem): DocsNavNode { if (isExternalItem(item)) return { label: item.label, href: item.link }; if (isGroupItem(item)) { const hub = groupHubSlug(item); return { label: item.label, href: hub ? `/docs/${hub}` : "/docs", slug: hub, children: item.items.map(itemToNode), }; } return { label: item.label, href: `/docs/${item.slug}`, slug: item.slug };}
// Every leaf slug under a set of items, descending into nested chapters.function leafSlugs(items: DocsNavItem[]): string[] { return items.flatMap((item) => { if (isExternalItem(item)) return []; if (isGroupItem(item)) return leafSlugs(item.items); return [item.slug]; });}
// A chapter's hub slug: the directory shared by all its pages. The Node SDK// chapter (pages under sdk/nodejs/*) โ "sdk/nodejs".function groupHubSlug(group: DocsNavGroup): string | undefined { const dirs = leafSlugs(group.items).map((s) => s.split("/").slice(0, -1)); if (dirs.length === 0) return undefined; let prefix = dirs[0]; for (const dir of dirs.slice(1)) { let i = 0; while (i < prefix.length && prefix[i] === dir[i]) i++; prefix = prefix.slice(0, i); } return prefix.length ? prefix.join("/") : undefined;}
// Slugs of every container node (sections + chapters) โ the synthetic hub URLs// that have no backing MDX file and must be statically generated as card grids.export function getDocsContainerSlugs(): string[] { const out: string[] = []; const walk = (node: DocsNavNode) => { for (const child of node.children ?? []) { if (!child.children?.length) continue; const slug = child.href.replace(/^\/docs\/?/, ""); if (slug) out.push(slug); walk(child); } }; walk(docsNavTree()); return out;}
// Root-to-target node trail (inclusive) โ the ancestor chain that drives nested// breadcrumbs. Every intermediate node is a navigable hub (section/chapter landing).export function findDocsTrail( node: DocsNavNode, href: string,): DocsNavNode[] | undefined { if (node.href === href) return [node]; for (const child of node.children ?? []) { const found = findDocsTrail(child, href); if (found) return [node, ...found]; } return undefined;}
// Depth-first lookup of a node by its href (e.g. "/docs", "/docs/concept").export function findDocsNode( node: DocsNavNode, href: string,): DocsNavNode | undefined { return findDocsTrail(node, href)?.at(-1);}
// Map a slug back to its section label (the value a doc's `category` must hold).export function sectionForSlug(slug: string): DocsSection | undefined { const has = (items: DocsNavItem[]): boolean => items.some((i) => { if (isExternalItem(i)) return false; if (isGroupItem(i)) return has(i.items); return i.slug === slug; }); for (const section of docsNav) { if (has(section.items)) return section.label as DocsSection; } return undefined;}