Skip to contentVibraUI

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

Install

npx shadcn@latest add @vibra/widget-engineering-status-service-uptime

Needs the @vibra registry in your components.json — set it up once.

Source

app/status/components/service-uptime.tsx
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>
  )
}
app/status/data.ts
/**
 * 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