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.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371import { allPlans } from "@openstatus/db/src/schema/plan/config";import type { AggregateRating, BlogPosting, BreadcrumbList, CollectionPage, FAQPage, Graph, HowTo, Organization, Person, Product, Review, Service, SoftwareApplication, TechArticle, Thing, WebPage, WithContext,} from "schema-dts";
import type { MDXData } from "../../content/utils";import { BASE_URL } from "./shared-metadata";
export const getJsonLDWebPage = ( input: MDXData | { name: string; url: string },): WithContext<WebPage> => { if (!("metadata" in input)) { return { "@context": "https://schema.org", "@type": "WebPage", name: input.name, url: input.url, }; } return { "@context": "https://schema.org", "@type": "WebPage", name: `${input.metadata.title} | openstatus`, headline: input.metadata.description, datePublished: input.metadata.publishedAt.toISOString(), dateModified: ( input.metadata.updatedAt ?? input.metadata.publishedAt ).toISOString(), mainEntityOfPage: { "@type": "WebPage", "@id": BASE_URL, }, };};
export const getJsonLDService = (input: { name: string; url: string;}): WithContext<Service> => { return { "@context": "https://schema.org", "@type": "Service", name: input.name, url: input.url, };};
export const getJsonLDBlogPosting = ( post: MDXData, basePath: string,): WithContext<BlogPosting> => { return { "@context": "https://schema.org", "@type": "BlogPosting", headline: post.metadata.title, datePublished: post.metadata.publishedAt.toISOString(), dateModified: ( post.metadata.updatedAt ?? post.metadata.publishedAt ).toISOString(), description: post.metadata.description, image: post.metadata.image ? `${BASE_URL}${post.metadata.image}` : `/api/og?title=${encodeURIComponent( post.metadata.title, )}&description=${encodeURIComponent( post.metadata.description, )}&category=${encodeURIComponent(post.metadata.category)}`, url: `${BASE_URL}/${basePath}/${post.slug}`, author: { "@type": "Person", name: post.metadata.author, }, };};
export const getJsonLDTechArticle = ( doc: MDXData,): WithContext<TechArticle> => { return { "@context": "https://schema.org", "@type": "TechArticle", headline: doc.metadata.title, description: doc.metadata.description, datePublished: doc.metadata.publishedAt.toISOString(), dateModified: ( doc.metadata.updatedAt ?? doc.metadata.publishedAt ).toISOString(), url: `${BASE_URL}${doc.href}`, author: { "@type": "Organization", name: "openstatus", url: BASE_URL, }, publisher: { "@type": "Organization", name: "openstatus", logo: { "@type": "ImageObject", url: `${BASE_URL}/assets/logos/OpenStatus-Logo.svg`, }, }, };};
export const getJsonLDOrganization = (): WithContext<Organization> => { return { "@context": "https://schema.org", "@type": "Organization", name: "openstatus", url: BASE_URL, logo: `${BASE_URL}/assets/logos/OpenStatus-Logo.svg`, sameAs: [ "https://github.com/openstatushq", "https://linkedin.com/company/openstatus", "https://bsky.app/profile/openstatus.dev", "https://x.com/openstatushq", ], // Same address the contact page and every email footer already carry โ // it is what lets an assistant tie the entity to the registered company. address: { "@type": "PostalAddress", streetAddress: "122 Rue Amelot", postalCode: "75011", addressLocality: "Paris", addressCountry: "FR", }, contactPoint: [ { "@type": "ContactPoint", contactType: "Support", email: "ping@openstatus.dev", }, ], };};
// Sourced from https://www.trustpilot.com/review/openstatus.devconst trustpilotReviews: Review[] = [ { "@type": "Review", reviewRating: { "@type": "Rating", ratingValue: 5, bestRating: 5, worstRating: 1, }, author: { "@type": "Person", name: "Johannes", }, datePublished: "2024-06-11", name: "Open source status page which works super well!", reviewBody: "Love that we have an open source alternative for our status page at Formbricks! Great team and execution, big fan of openstatus!", },];
const trustpilotAggregateRating: AggregateRating = { "@type": "AggregateRating", ratingValue: 5, bestRating: 5, worstRating: 1, reviewCount: trustpilotReviews.length,};
export const getJsonLDProduct = (): WithContext<Product> => { return { "@context": "https://schema.org", "@type": "Product", name: "openstatus", description: "The open-source status page and uptime monitoring platform built for infra as code. Declare monitors in code and let your agents update them.", image: `${BASE_URL}/assets/logos/OpenStatus-Logo.svg`, url: BASE_URL, brand: { "@type": "Brand", name: "openstatus", logo: `${BASE_URL}/assets/logos/OpenStatus-Logo.svg`, }, offers: Object.entries(allPlans).map(([_, value]) => ({ "@type": "Offer", price: value.price.monthly.USD, name: value.title, priceCurrency: "USD", availability: "https://schema.org/InStock", })), aggregateRating: trustpilotAggregateRating, review: trustpilotReviews, };};
export const getJsonLDSoftwareApplication = (): WithContext<SoftwareApplication> => { return { "@context": "https://schema.org", "@type": "SoftwareApplication", name: "openstatus", url: BASE_URL, description: "The open-source status page and uptime monitoring platform built for infra as code. Declare monitors in code and let your agents update them.", applicationCategory: "BusinessApplication", applicationSubCategory: "Status Pages & Uptime Monitoring", operatingSystem: "Web, Self-hosted", offers: Object.entries(allPlans).map(([_, value]) => ({ "@type": "Offer", price: value.price.monthly.USD, name: value.title, priceCurrency: "USD", availability: "https://schema.org/InStock", })), featureList: [ "Branded status pages with custom domains", "Multi-region uptime monitoring from 28 locations", "Incident communication and subscriber notifications", "API monitoring (REST, GraphQL)", "SOC 2 compliance-ready", "Monitoring as code", "Self-hosting option", "Open-source", ], screenshot: `${BASE_URL}/assets/landing/dashboard.png`, aggregateRating: trustpilotAggregateRating, review: trustpilotReviews, }; };
export const getJsonLDPerson = (item: { name: string; url?: string;}): WithContext<Person> => { return { "@context": "https://schema.org", "@type": "Person", name: item.name, url: item.url, };};
type ItemListEntry = { name: string; url: string; description?: string };
export function getJsonLDItemList( items: MDXData[], prefix: string,): WithContext<CollectionPage>;export function getJsonLDItemList( items: ItemListEntry[],): WithContext<CollectionPage>;export function getJsonLDItemList( items: MDXData[] | ItemListEntry[], prefix?: string,): WithContext<CollectionPage> { // MDXData overload maps slug โ URL via prefix; the entry overload carries // pre-resolved URLs (used where card href โ slug, e.g. docs section hubs). const entries: ItemListEntry[] = prefix !== undefined ? (items as MDXData[]).map((item) => ({ name: item.metadata.title, url: `${BASE_URL}${prefix}/${item.slug}`, description: item.metadata.description, })) : (items as ItemListEntry[]); return { "@context": "https://schema.org", "@type": "CollectionPage", mainEntity: { "@type": "ItemList", numberOfItems: entries.length, itemListElement: entries.map((entry, index) => ({ "@type": "ListItem", position: index + 1, url: entry.url, name: entry.name, description: entry.description, })), }, };}
export const getJsonLDBreadcrumbList = ( items: { name: string; url: string }[],): WithContext<BreadcrumbList> => { return { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: items.map((item, index) => ({ "@type": "ListItem", position: index + 1, name: item.name, item: item.url, })), };};
export function getJsonLDFAQPage( input: MDXData | { question: string; answer: string }[],): WithContext<FAQPage> | null { const faq = Array.isArray(input) ? input : input.metadata.faq; if (!faq) { return null; } return { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: faq.map((item) => ({ "@type": "Question", name: item.question, acceptedAnswer: { "@type": "Answer", text: item.answer, }, })), };}
export const getJsonLDHowTo = (post: MDXData): WithContext<HowTo> | null => { if (!post.metadata.howto) { return null; }
return { "@context": "https://schema.org", "@type": "HowTo", name: post.metadata.title, description: post.metadata.description, totalTime: post.metadata.howto.totalTime, step: post.metadata.howto.steps.map((step, index) => ({ "@type": "HowToStep", position: index + 1, name: step.name, text: step.text, image: step.image ?? undefined, url: step.url ?? undefined, })), };};
/** Merge schema objects into a single `@graph`, stripping their per-item `@context`. */export const createJsonLDGraph = ( items: (WithContext<Thing> | null)[],): Graph => { const graphItems = items .filter((item): item is WithContext<Thing> => item !== null) .map( (item) => Object.fromEntries( Object.entries(item).filter(([key]) => key !== "@context"), ) as Thing, );
return { "@context": "https://schema.org", "@graph": graphItems, };};