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: "Learn how to create your status page, monitor your endpoints, and configure notifications.", 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;}