Service availability
Every service with its state now and ninety days of availability behind it, a strip each over one shared date legend; reads services().
Preview
import { StatusBadge, type StatusVariant } from "@/components/ui/status-badge"
import { type ServiceStatus } from "@/components/ui/system-status"
import { UptimeBar } from "@/components/ui/uptime-bar"
import { historyWindow, services, type StatusService } from "../data"
// The status-page vocabulary, in the pill's own tones. UI vocabulary, so it
// lives with the thing that draws it rather than in data.ts.
const TONES: Record<ServiceStatus, StatusVariant> = {
operational: "success",
degraded: "warning",
outage: "danger",
maintenance: "info",
}
const LABELS: Record<ServiceStatus, string> = {
operational: "Operational",
degraded: "Degraded",
outage: "Outage",
maintenance: "Maintenance",
}
function ServiceRow({ service }: { service: StatusService }) {
return (
<li className="px-4 py-3.5">
<UptimeBar
days={service.days}
// Stated, not counted off the strip: this is the availability the
// probes measured, and the strip marks whole days.
uptime={service.uptime}
size="sm"
// One legend under the whole list rather than eight identical ones.
showLegend={false}
// The label is a name and a pill, so the strip's own summary — which
// has to be one string — takes the service name from here.
labelText={service.name}
label={
<span className="flex min-w-0 items-center gap-2">
<span className="truncate text-sm font-medium text-foreground">{service.name}</span>
<StatusBadge
status={service.status}
variant={TONES[service.status]}
label={LABELS[service.status]}
size="sm"
/>
</span>
}
/>
</li>
)
}
/** Every service, with the run of days behind it. */
export function ServiceUptime() {
const rows = services()
const window = historyWindow()
return (
<div data-widget="widget-engineering-status-service-uptime" className="flex flex-col gap-2">
<ul className="flex flex-col divide-y panel">
{rows.map((service) => (
<ServiceRow key={service.id} service={service} />
))}
</ul>
{/* One legend for the eight strips above, which all cover the same run. */}
<div className="flex items-baseline justify-between gap-3 px-1 text-xs text-muted-foreground">
<span>{window.from}</span>
<span>{window.to}</span>
</div>
</div>
)
}Install
$
npx shadcn@latest add @vibra/widget-engineering-status-service-uptimeNeeds the @vibra registry in your components.json — set it up once.
Source
import { StatusBadge, type StatusVariant } from "@/components/ui/status-badge"
import { type ServiceStatus } from "@/components/ui/system-status"
import { UptimeBar } from "@/components/ui/uptime-bar"
import { historyWindow, services, type StatusService } from "../data"
// The status-page vocabulary, in the pill's own tones. UI vocabulary, so it
// lives with the thing that draws it rather than in data.ts.
const TONES: Record<ServiceStatus, StatusVariant> = {
operational: "success",
degraded: "warning",
outage: "danger",
maintenance: "info",
}
const LABELS: Record<ServiceStatus, string> = {
operational: "Operational",
degraded: "Degraded",
outage: "Outage",
maintenance: "Maintenance",
}
function ServiceRow({ service }: { service: StatusService }) {
return (
<li className="px-4 py-3.5">
<UptimeBar
days={service.days}
// Stated, not counted off the strip: this is the availability the
// probes measured, and the strip marks whole days.
uptime={service.uptime}
size="sm"
// One legend under the whole list rather than eight identical ones.
showLegend={false}
// The label is a name and a pill, so the strip's own summary — which
// has to be one string — takes the service name from here.
labelText={service.name}
label={
<span className="flex min-w-0 items-center gap-2">
<span className="truncate text-sm font-medium text-foreground">{service.name}</span>
<StatusBadge
status={service.status}
variant={TONES[service.status]}
label={LABELS[service.status]}
size="sm"
/>
</span>
}
/>
</li>
)
}
/** Every service, with the run of days behind it. */
export function ServiceUptime() {
const rows = services()
const window = historyWindow()
return (
<div data-widget="widget-engineering-status-service-uptime" className="flex flex-col gap-2">
<ul className="flex flex-col divide-y panel">
{rows.map((service) => (
<ServiceRow key={service.id} service={service} />
))}
</ul>
{/* One legend for the eight strips above, which all cover the same run. */}
<div className="flex items-baseline justify-between gap-3 px-1 text-xs text-muted-foreground">
<span>{window.from}</span>
<span>{window.to}</span>
</div>
</div>
)
}/**
* What the public status page reads. Everything comes from `db`: the services
* and their 90 days of uptime from `db.services`, the incidents and the update
* messages under them from `db.incidents`. Nothing is written here — a status
* page is a read of the platform, not a control over it — so this file is
* selectors alone, and "now" is `REFERENCE_DATE`.
*/
import { db, REFERENCE_DATE, type Incident, type Service } from "@/lib/sample-data"
import { type IncidentSeverity } from "@/components/ui/incident-list"
import { type ServiceStatus } from "@/components/ui/system-status"
import { type UptimeDay } from "@/components/ui/uptime-bar"
// The services are a read-only record; the incidents are read per call, never
// held at module scope — one declared on the incidents page since the server
// started is on this page the next time it is asked for.
const SERVICES = db.services.all()
const incidents = (): Incident[] => db.incidents.all()
const DAY_MS = 86_400_000
/**
* Every date and time on this page is written in UTC, because the page says so
* under its own title and because a reader comparing an incident against their
* own logs needs one zone to compare in. These four are the only formatters the
* block uses: a formatter with no `timeZone` renders in whatever zone the
* browser is in, which west of UTC files September 4th's incidents under a
* heading that reads September 3rd.
*/
const DAY_HEADING = new Intl.DateTimeFormat("en-US", { dateStyle: "long", timeZone: "UTC" })
const DATE_ONLY = new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZone: "UTC" })
const TIME_ONLY = new Intl.DateTimeFormat("en-US", {
hour: "2-digit",
minute: "2-digit",
hourCycle: "h23",
timeZone: "UTC",
})
const STAMP = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hourCycle: "h23",
timeZone: "UTC",
})
/** The heading over one day of incident history: "September 4, 2026". */
export function formatDayHeading(at: Date): string {
return DAY_HEADING.format(at)
}
/** A date on its own, for the legend under the uptime strips. */
export function formatUtcDate(at: Date): string {
return DATE_ONLY.format(at)
}
/** A clock time, for an update read under a heading that already gives the day. */
export function formatUtcTime(at: Date): string {
return `${TIME_ONLY.format(at)} UTC`
}
/**
* A date and a time. An incident that starts near midnight publishes its next
* update the following day, so every stamp on a timeline carries its date.
*/
export function formatUtcStamp(at: Date): string {
return `${STAMP.format(at)} UTC`
}
// Five status words in the data, four on a status page: both kinds of outage
// read as "outage" to someone who cannot use the product either way.
const STATUS: Record<Service["status"], ServiceStatus> = {
operational: "operational",
degraded: "degraded",
partial_outage: "outage",
major_outage: "outage",
maintenance: "maintenance",
}
// Worst first, so a region takes the status of its unhappiest service rather
// than an average that would hide it.
const SEVERITY_ORDER: ServiceStatus[] = ["outage", "degraded", "maintenance", "operational"]
export type StatusRegion = { name: string; status: ServiceStatus; description: string }
/**
* Where the platform runs, worst region first. The banner above them takes the
* worst status of the lot, so an outage in one region is never averaged away
* by three healthy ones.
*/
export function regions(): StatusRegion[] {
const names = [...new Set(SERVICES.map((service) => service.region))]
return names
.map((name) => {
const here = SERVICES.filter((service) => service.region === name)
const statuses = here.map((service) => STATUS[service.status])
const status = SEVERITY_ORDER.find((candidate) => statuses.includes(candidate)) ?? "operational"
return {
name,
status,
description: `${here.length} ${here.length === 1 ? "service" : "services"}`,
}
})
.sort((a, b) => SEVERITY_ORDER.indexOf(a.status) - SEVERITY_ORDER.indexOf(b.status))
}
/**
* A day is up above 99.5%, degraded down to 90%, and down below that. The
* floor is deliberately low for a public page: a red day is a promise that the
* product was unusable, and an hour of elevated errors inside a working day is
* degradation, not an outage. Say "down" too readily and every strip reads as
* a wall of red that nobody trusts.
*/
function dayStatus(uptime: number): UptimeDay["status"] {
if (uptime >= 99.5) return "up"
if (uptime >= 90) return "degraded"
return "down"
}
const DAY_LABELS: Record<UptimeDay["status"], (uptime: number) => string> = {
up: () => "No incidents",
degraded: (uptime) => `${uptime.toFixed(2)}% availability`,
down: (uptime) => `${uptime.toFixed(2)}% availability`,
none: () => "Not measured",
}
export type StatusService = {
id: string
name: string
status: ServiceStatus
/** The share of the 90 days that stayed up, as the strip's own headline. */
uptime: number
days: UptimeDay[]
}
/** Every service, in the order the platform is usually described. */
export function services(): StatusService[] {
return SERVICES.map((service) => {
const history = service.uptime90d
const start = REFERENCE_DATE.getTime() - (history.length - 1) * DAY_MS
const average = history.reduce((sum, day) => sum + day, 0) / history.length
return {
id: service.id,
name: service.name,
status: STATUS[service.status],
// Stated rather than left to UptimeBar's own count of good and bad days:
// this is the availability the probes actually measured.
uptime: Math.round(average * 100) / 100,
days: history.map((value, index) => {
const status = dayStatus(value)
return {
date: new Date(start + index * DAY_MS).toISOString().slice(0, 10),
status,
label: DAY_LABELS[status](value),
}
}),
}
})
}
/** How long the strips above cover, for the line under them. */
export function historyWindow(): { from: string; to: string; days: number } {
const days = SERVICES[0]?.uptime90d.length ?? 0
return {
from: formatUtcDate(new Date(REFERENCE_DATE.getTime() - (days - 1) * DAY_MS)),
to: formatUtcDate(REFERENCE_DATE),
days,
}
}
const SEVERITY: Record<Incident["severity"], IncidentSeverity> = {
sev1: "critical",
sev2: "major",
sev3: "minor",
}
const SERVICE_NAME_BY_ID = new Map(SERVICES.map((service) => [service.id, service.name]))
export type StatusUpdate = { at: Date; status: Incident["status"]; message: string }
export type StatusIncident = {
id: string
title: string
severity: IncidentSeverity
status: Incident["status"]
startedAt: Date
resolvedAt?: Date
affected: string[]
updates: StatusUpdate[]
}
function toStatusIncident(incident: Incident): StatusIncident {
return {
id: incident.id,
title: incident.title,
severity: SEVERITY[incident.severity],
status: incident.status,
startedAt: incident.startedAt,
resolvedAt: incident.resolvedAt,
affected: incident.serviceIds.map((id) => SERVICE_NAME_BY_ID.get(id) ?? id),
// Newest first: the reader wants where it got to, then how it got there.
updates: [...incident.updates].sort((a, b) => b.at.getTime() - a.at.getTime()),
}
}
/** Everything still open, newest first. */
export function activeIncidents(): StatusIncident[] {
return incidents().filter((incident) => incident.status !== "resolved")
.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime())
.map(toStatusIncident)
}
/** How many days of history the page publishes. */
export const HISTORY_DAYS = 14
export type IncidentDay = { key: string; label: string; incidents: StatusIncident[] }
/**
* The last fourteen days, newest first, each with what closed on it. A day
* with nothing on it still gets a row — that a status page says so is the
* point of publishing one.
*/
export function incidentHistory(): IncidentDay[] {
const closed = incidents().filter((incident) => incident.status === "resolved")
const midnight = Date.UTC(
REFERENCE_DATE.getUTCFullYear(),
REFERENCE_DATE.getUTCMonth(),
REFERENCE_DATE.getUTCDate()
)
return Array.from({ length: HISTORY_DAYS }, (_, index) => {
const at = new Date(midnight - index * DAY_MS)
const key = at.toISOString().slice(0, 10)
return {
key,
label: formatDayHeading(at),
incidents: closed
.filter((incident) => incident.startedAt.toISOString().slice(0, 10) === key)
.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime())
.map(toStatusIncident),
}
})
}Its page
On its page the card sits among the rest of the dashboard and shares its range and its data with them.
From the Public status page page