{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "status-monitoring",
  "title": "Status Monitoring",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/status-monitoring/StatusMonitoring.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { SectionCard } from '@/components/ui/section-card'\n\ntype DayStatus = 'up' | 'degraded' | 'down' | 'maintenance' | 'none'\n\ntype IncidentSeverity = 'minor' | 'major' | 'critical'\n\ninterface ServiceStatus {\n  name: string\n  /** One entry per day, oldest first. Expected length: 90. */\n  days: DayStatus[]\n}\n\ninterface MonitoringIncident {\n  id: string\n  date: Date\n  title: string\n  description: string\n  duration: string\n  severity: IncidentSeverity\n  resolved?: boolean\n}\n\nconst props = defineProps<{\n  services?: ServiceStatus[]\n  incidents?: MonitoringIncident[]\n  class?: HTMLAttributes['class']\n}>()\n\nconst DAY_COUNT = 90\n\n// Deterministic stubs (no Math.random) so SSR and client markup match exactly.\nfunction buildDays(blips: Array<{ ago: number; length: number; status: DayStatus }> = []): DayStatus[] {\n  const days: DayStatus[] = Array.from({ length: DAY_COUNT }, () => 'up')\n  for (const blip of blips) {\n    for (let k = 0; k < blip.length; k++) {\n      const idx = DAY_COUNT - 1 - blip.ago - k\n      if (idx >= 0 && idx < DAY_COUNT) days[idx] = blip.status\n    }\n  }\n  return days\n}\n\nconst stubServices: ServiceStatus[] = [\n  {\n    name: 'API Gateway',\n    days: buildDays([\n      { ago: 34, length: 1, status: 'down' },\n      { ago: 33, length: 1, status: 'degraded' },\n    ]),\n  },\n  { name: 'Web application', days: buildDays() },\n  { name: 'Webhooks', days: buildDays([{ ago: 61, length: 1, status: 'degraded' }]) },\n]\n\nconst now = new Date()\nconst daysAgoDate = (d: number) => new Date(now.getTime() - d * 86_400_000)\n\nconst stubIncidents: MonitoringIncident[] = [\n  {\n    id: 'i1',\n    date: daysAgoDate(33),\n    title: 'Elevated error rates on API Gateway',\n    description: 'A bad deploy caused intermittent 502s for roughly forty minutes. Rolled back; error rates recovered.',\n    duration: 'Lasted 42 minutes',\n    severity: 'major',\n    resolved: true,\n  },\n  {\n    id: 'i2',\n    date: daysAgoDate(34),\n    title: 'API Gateway unavailable',\n    description: 'Provider network maintenance dropped traffic between 02:14 and 02:51 UTC.',\n    duration: 'Lasted 37 minutes',\n    severity: 'critical',\n    resolved: true,\n  },\n  {\n    id: 'i3',\n    date: daysAgoDate(61),\n    title: 'Delayed webhook deliveries',\n    description: 'A queue backlog delayed deliveries by up to six minutes. The backlog drained on its own.',\n    duration: 'Lasted 1 hour, 12 minutes',\n    severity: 'minor',\n    resolved: true,\n  },\n]\n\nconst services = computed(() => props.services ?? stubServices)\n\nconst incidentList = computed(() =>\n  [...(props.incidents ?? stubIncidents)].sort((a, b) => b.date.getTime() - a.date.getTime()),\n)\n\ntype OverallState = 'operational' | 'degraded' | 'outage'\n\nconst overall = computed<OverallState>(() => {\n  const latest = services.value.map((s) => [...s.days].reverse().find((d) => d !== 'none'))\n  if (latest.includes('down')) return 'outage'\n  if (latest.includes('degraded')) return 'degraded'\n  return 'operational'\n})\n\nconst bannerMeta: Record<OverallState, { label: string; box: string; text: string; dot: string }> = {\n  operational: {\n    label: 'All systems operational',\n    box: 'border-success/25 bg-success/10',\n    text: 'text-success',\n    dot: 'bg-success',\n  },\n  degraded: {\n    label: 'Degraded performance',\n    box: 'border-warning/25 bg-warning/10',\n    text: 'text-warning',\n    dot: 'bg-warning',\n  },\n  outage: {\n    label: 'Partial outage',\n    box: 'border-destructive/25 bg-destructive/10',\n    text: 'text-destructive',\n    dot: 'bg-destructive',\n  },\n}\n\nconst dayClass: Record<DayStatus, string> = {\n  up: 'bg-success',\n  degraded: 'bg-warning',\n  down: 'bg-destructive',\n  maintenance: 'bg-info',\n  none: 'bg-muted',\n}\n\nconst dayLabel: Record<DayStatus, string> = {\n  up: 'Operational',\n  degraded: 'Degraded',\n  down: 'Outage',\n  maintenance: 'Maintenance',\n  none: 'No data',\n}\n\n// Built once per mount so 90 segments × N services never rebuild Date objects during render.\nconst dayStamps = Array.from({ length: DAY_COUNT }, (_, i) =>\n  daysAgoDate(DAY_COUNT - 1 - i).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }),\n)\n\nconst updatedStamp = now.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })\n\nfunction tooltipFor(days: DayStatus[], i: number): string {\n  return `${dayStamps[i]} · ${dayLabel[days[i]]}`\n}\n\nfunction uptimePct(days: DayStatus[]): string {\n  const counted = days.filter((d) => d !== 'none')\n  if (counted.length === 0) return '—'\n  const up = counted.filter((d) => d === 'up').length\n  return `${((up / counted.length) * 100).toFixed(2)}%`\n}\n\nfunction formatDay(d: Date): string {\n  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })\n}\n\nconst severityVariant: Record<IncidentSeverity, 'info' | 'warning' | 'destructive'> = {\n  minor: 'info',\n  major: 'warning',\n  critical: 'destructive',\n}\n\nconst legend = (Object.keys(dayLabel) as DayStatus[]).map((status) => ({ status, label: dayLabel[status] }))\n</script>\n\n<template>\n  <div :class=\"cn('space-y-4', props.class)\" data-slot=\"status-monitoring\">\n    <div role=\"status\" :class=\"['flex items-center gap-3 rounded-xl border px-4 py-3', bannerMeta[overall].box]\">\n      <span class=\"relative flex size-2.5 shrink-0\">\n        <span :class=\"['absolute inline-flex h-full w-full rounded-full opacity-60', bannerMeta[overall].dot]\" />\n        <span :class=\"['relative inline-flex size-2.5 rounded-full', bannerMeta[overall].dot]\" />\n      </span>\n      <p :class=\"['text-sm font-medium', bannerMeta[overall].text]\">{{ bannerMeta[overall].label }}</p>\n      <span class=\"text-muted-foreground ml-auto text-xs whitespace-nowrap\">Updated {{ updatedStamp }}</span>\n    </div>\n\n    <SectionCard title=\"Service status\" description=\"Daily availability over the last 90 days.\">\n      <template #header-action>\n        <ul class=\"text-muted-foreground hidden flex-wrap items-center gap-3 text-xs md:flex\">\n          <li v-for=\"item in legend\" :key=\"item.status\" class=\"flex shrink-0 items-center gap-1.5 whitespace-nowrap\">\n            <span :class=\"['size-2 rounded-full', dayClass[item.status]]\" />\n            {{ item.label }}\n          </li>\n        </ul>\n      </template>\n      <ul class=\"divide-y\">\n        <li v-for=\"(s, si) in services\" :key=\"s.name\" :class=\"['space-y-2 py-4', si === 0 ? 'pt-0' : '']\">\n          <div class=\"flex flex-wrap items-baseline justify-between gap-4\">\n            <p class=\"text-sm font-medium\">{{ s.name }}</p>\n            <p class=\"text-muted-foreground text-sm tabular-nums\">{{ uptimePct(s.days) }}</p>\n          </div>\n          <div\n            class=\"flex w-full gap-px overflow-hidden sm:gap-[2px]\"\n            role=\"img\"\n            :aria-label=\"`${s.name} uptime ${uptimePct(s.days)} over the last 90 days`\"\n          >\n            <span\n              v-for=\"(d, di) in s.days\"\n              :key=\"di\"\n              :class=\"['h-6 w-full min-w-px flex-1 rounded-sm transition-transform hover:scale-y-125', dayClass[d]]\"\n              :title=\"tooltipFor(s.days, di)\"\n            />\n          </div>\n        </li>\n      </ul>\n      <p class=\"text-muted-foreground mt-4 flex justify-between text-xs\">\n        <span>90 days ago</span>\n        <span>Today</span>\n      </p>\n    </SectionCard>\n\n    <SectionCard title=\"Incident history\" description=\"Reported incidents over the same period.\">\n      <ul v-if=\"incidentList.length\" class=\"divide-y\">\n        <li v-for=\"(inc, ii) in incidentList\" :key=\"inc.id\" :class=\"['py-4', ii === 0 ? 'pt-0' : '']\">\n          <p class=\"text-muted-foreground text-xs font-medium\">{{ formatDay(inc.date) }}</p>\n          <div class=\"mt-1.5 flex flex-wrap items-center gap-2\">\n            <Badge :variant=\"inc.resolved ? 'success' : severityVariant[inc.severity]\" class=\"capitalize\">\n              {{ inc.resolved ? 'Resolved' : inc.severity }}\n            </Badge>\n            <p class=\"text-sm font-medium\">{{ inc.title }}</p>\n            <span class=\"text-muted-foreground ml-auto text-xs whitespace-nowrap\">{{ inc.duration }}</span>\n          </div>\n          <p class=\"text-muted-foreground mt-1 text-sm leading-relaxed\">{{ inc.description }}</p>\n        </li>\n      </ul>\n      <div v-else class=\"flex flex-col items-center justify-center px-6 py-10 text-center\">\n        <p class=\"text-sm font-medium\">No incidents reported</p>\n        <p class=\"text-muted-foreground mt-0.5 text-xs\">Every day in this window has been quiet.</p>\n      </div>\n    </SectionCard>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/status-monitoring/StatusMonitoring.vue"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/section-card.json"
  ],
  "description": "Uptime page. Banner derives All systems operational / Degraded performance / Partial outage from the newest day of each service. Per-service rows render a 90-segment day bar (success/warning/destructive/info/muted per status) with hover tooltips and a right-aligned uptime percentage, followed by an incident timeline with severity badges. Pass `services` (name + days array of up/degraded/down/maintenance/none) and `incidents` to replace the stubs.",
  "categories": [
    "devops",
    "dashboard",
    "analytics"
  ]
}