{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "uptime-monitor",
  "title": "Uptime Monitor",
  "description": "Status-page style map of edge network locations with live health, latency, and uptime.",
  "dependencies": [
    "maplibre-gl"
  ],
  "registryDependencies": [
    "@mapcn/map"
  ],
  "files": [
    {
      "path": "src/registry/blocks/uptime-monitor/page.tsx",
      "content": "\"use client\";\n\nimport { Map, MapControls, MapGeoJSON } from \"@/components/ui/map\";\nimport { EdgeNodeMarker } from \"./components/edge-node-marker\";\nimport { StatusSidebar } from \"./components/status-sidebar\";\nimport { edgeNodes, mapView, WORLD_GEOJSON } from \"./data\";\n\nexport default function Page() {\n  return (\n    <div className=\"flex min-h-screen items-center justify-center p-4\">\n      <div className=\"bg-card flex h-[500px] w-full max-w-4xl overflow-hidden rounded-xl border shadow-sm\">\n        <StatusSidebar nodes={edgeNodes} />\n\n        <div className=\"relative min-w-0 flex-1\">\n          <Map\n            blank\n            center={mapView.center}\n            zoom={mapView.zoom}\n            minZoom={mapView.minZoom}\n            maxZoom={mapView.maxZoom}\n            scrollZoom={false}\n            dragRotate={false}\n            pitchWithRotate={false}\n          >\n            <MapGeoJSON data={WORLD_GEOJSON} linePaint={false} />\n\n            {edgeNodes.map((node) => (\n              <EdgeNodeMarker key={node.id} node={node} />\n            ))}\n\n            <MapControls className=\"bottom-2\" />\n          </Map>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:page",
      "target": "app/uptime-monitor/page.tsx"
    },
    {
      "path": "src/registry/blocks/uptime-monitor/data.ts",
      "content": "export type EdgeStatus = \"operational\" | \"degraded\" | \"down\";\n\nexport interface EdgeNode {\n  id: string;\n  city: string;\n  region: string;\n  lng: number;\n  lat: number;\n  status: EdgeStatus;\n  latency: number;\n  uptime: number;\n}\n\nexport const statusMeta: Record<EdgeStatus, { dot: string; text: string }> = {\n  operational: {\n    dot: \"bg-emerald-500\",\n    text: \"text-emerald-600 dark:text-emerald-400\",\n  },\n  degraded: {\n    dot: \"bg-amber-500\",\n    text: \"text-amber-600 dark:text-amber-400\",\n  },\n  down: {\n    dot: \"bg-red-500\",\n    text: \"text-red-600 dark:text-red-400\",\n  },\n};\n\n/** Country borders served from a public CDN — swap in your own GeoJSON. */\nexport const WORLD_GEOJSON =\n  \"https://cdn.jsdelivr.net/gh/nvkelso/natural-earth-vector@v5.1.2/geojson/ne_110m_admin_0_countries.geojson\";\n\nexport const mapView = {\n  center: [5, 28] as [number, number],\n  zoom: 0.45,\n  minZoom: 0.45,\n  maxZoom: 2,\n};\n\n/** Edge network points of presence. */\nexport const edgeNodes: EdgeNode[] = [\n  {\n    id: \"iad\",\n    city: \"Washington, D.C.\",\n    region: \"North America\",\n    lng: -77.0369,\n    lat: 38.9072,\n    status: \"operational\",\n    latency: 24,\n    uptime: 99.99,\n  },\n  {\n    id: \"sfo\",\n    city: \"San Francisco\",\n    region: \"North America\",\n    lng: -122.4194,\n    lat: 37.7749,\n    status: \"operational\",\n    latency: 31,\n    uptime: 99.98,\n  },\n  {\n    id: \"yyz\",\n    city: \"Toronto\",\n    region: \"North America\",\n    lng: -79.3832,\n    lat: 43.6532,\n    status: \"degraded\",\n    latency: 88,\n    uptime: 99.21,\n  },\n  {\n    id: \"gru\",\n    city: \"São Paulo\",\n    region: \"South America\",\n    lng: -46.6333,\n    lat: -23.5505,\n    status: \"operational\",\n    latency: 47,\n    uptime: 99.95,\n  },\n  {\n    id: \"lhr\",\n    city: \"London\",\n    region: \"Europe\",\n    lng: -0.1276,\n    lat: 51.5074,\n    status: \"operational\",\n    latency: 19,\n    uptime: 99.99,\n  },\n  {\n    id: \"fra\",\n    city: \"Frankfurt\",\n    region: \"Europe\",\n    lng: 8.6821,\n    lat: 50.1109,\n    status: \"operational\",\n    latency: 22,\n    uptime: 99.97,\n  },\n  {\n    id: \"cdg\",\n    city: \"Paris\",\n    region: \"Europe\",\n    lng: 2.3522,\n    lat: 48.8566,\n    status: \"down\",\n    latency: 0,\n    uptime: 97.84,\n  },\n  {\n    id: \"jnb\",\n    city: \"Johannesburg\",\n    region: \"Africa\",\n    lng: 28.0473,\n    lat: -26.2041,\n    status: \"operational\",\n    latency: 64,\n    uptime: 99.92,\n  },\n  {\n    id: \"bom\",\n    city: \"Mumbai\",\n    region: \"Asia Pacific\",\n    lng: 72.8777,\n    lat: 19.076,\n    status: \"operational\",\n    latency: 41,\n    uptime: 99.96,\n  },\n  {\n    id: \"sin\",\n    city: \"Singapore\",\n    region: \"Asia Pacific\",\n    lng: 103.8198,\n    lat: 1.3521,\n    status: \"degraded\",\n    latency: 73,\n    uptime: 99.45,\n  },\n  {\n    id: \"nrt\",\n    city: \"Tokyo\",\n    region: \"Asia Pacific\",\n    lng: 139.6917,\n    lat: 35.6895,\n    status: \"operational\",\n    latency: 28,\n    uptime: 99.98,\n  },\n  {\n    id: \"syd\",\n    city: \"Sydney\",\n    region: \"Asia Pacific\",\n    lng: 151.2093,\n    lat: -33.8688,\n    status: \"operational\",\n    latency: 52,\n    uptime: 99.94,\n  },\n  {\n    id: \"lax\",\n    city: \"Los Angeles\",\n    region: \"North America\",\n    lng: -118.2437,\n    lat: 34.0522,\n    status: \"operational\",\n    latency: 33,\n    uptime: 99.97,\n  },\n  {\n    id: \"ord\",\n    city: \"Chicago\",\n    region: \"North America\",\n    lng: -87.6298,\n    lat: 41.8781,\n    status: \"operational\",\n    latency: 27,\n    uptime: 99.98,\n  },\n  {\n    id: \"scl\",\n    city: \"Santiago\",\n    region: \"South America\",\n    lng: -70.6693,\n    lat: -33.4489,\n    status: \"operational\",\n    latency: 58,\n    uptime: 99.91,\n  },\n  {\n    id: \"ams\",\n    city: \"Amsterdam\",\n    region: \"Europe\",\n    lng: 4.9041,\n    lat: 52.3676,\n    status: \"operational\",\n    latency: 21,\n    uptime: 99.98,\n  },\n  {\n    id: \"mad\",\n    city: \"Madrid\",\n    region: \"Europe\",\n    lng: -3.7038,\n    lat: 40.4168,\n    status: \"degraded\",\n    latency: 79,\n    uptime: 99.32,\n  },\n  {\n    id: \"arn\",\n    city: \"Stockholm\",\n    region: \"Europe\",\n    lng: 18.0686,\n    lat: 59.3293,\n    status: \"operational\",\n    latency: 26,\n    uptime: 99.96,\n  },\n  {\n    id: \"dxb\",\n    city: \"Dubai\",\n    region: \"Middle East\",\n    lng: 55.2708,\n    lat: 25.2048,\n    status: \"operational\",\n    latency: 44,\n    uptime: 99.95,\n  },\n  {\n    id: \"hkg\",\n    city: \"Hong Kong\",\n    region: \"Asia Pacific\",\n    lng: 114.1694,\n    lat: 22.3193,\n    status: \"operational\",\n    latency: 36,\n    uptime: 99.97,\n  },\n  {\n    id: \"icn\",\n    city: \"Seoul\",\n    region: \"Asia Pacific\",\n    lng: 126.978,\n    lat: 37.5665,\n    status: \"operational\",\n    latency: 30,\n    uptime: 99.98,\n  },\n  {\n    id: \"los\",\n    city: \"Lagos\",\n    region: \"Africa\",\n    lng: 3.3792,\n    lat: 6.5244,\n    status: \"down\",\n    latency: 0,\n    uptime: 98.12,\n  },\n];\n\nexport interface NetworkSummary {\n  status: EdgeStatus;\n  label: string;\n  operational: number;\n  total: number;\n  avgUptime: number;\n}\n\n/** Derive the overall network health from the individual edge nodes. */\nexport function getNetworkSummary(nodes: EdgeNode[]): NetworkSummary {\n  const total = nodes.length;\n  const operational = nodes.filter((n) => n.status === \"operational\").length;\n  const hasDown = nodes.some((n) => n.status === \"down\");\n  const hasDegraded = nodes.some((n) => n.status === \"degraded\");\n\n  const status: EdgeStatus = hasDown\n    ? \"down\"\n    : hasDegraded\n      ? \"degraded\"\n      : \"operational\";\n\n  const label = hasDown\n    ? \"Partial outage\"\n    : hasDegraded\n      ? \"Degraded performance\"\n      : \"All systems operational\";\n\n  const avgUptime =\n    nodes.reduce((sum, n) => sum + n.uptime, 0) / Math.max(total, 1);\n\n  return { status, label, operational, total, avgUptime };\n}\n",
      "type": "registry:component",
      "target": "app/uptime-monitor/data.ts"
    },
    {
      "path": "src/registry/blocks/uptime-monitor/components/edge-node-marker.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { MapMarker, MarkerContent, MarkerTooltip } from \"@/components/ui/map\";\nimport { statusMeta, type EdgeNode } from \"../data\";\n\ninterface EdgeNodeMarkerProps {\n  node: EdgeNode;\n}\n\nexport function EdgeNodeMarker({ node }: EdgeNodeMarkerProps) {\n  const meta = statusMeta[node.status];\n\n  return (\n    <MapMarker longitude={node.lng} latitude={node.lat}>\n      <MarkerContent>\n        <div className=\"group flex size-4 items-center justify-center\">\n          <span\n            className={cn(\n              \"inline-flex size-2 rounded-full transition-transform group-hover:scale-125\",\n              meta.dot,\n            )}\n          />\n        </div>\n      </MarkerContent>\n      <MarkerTooltip\n        offset={10}\n        className=\"bg-popover text-popover-foreground min-w-28 border p-2\"\n      >\n        <div className=\"flex items-center gap-1.5\">\n          <span className={cn(\"size-1.5 rounded-full\", meta.dot)} />\n          <span className=\"text-[11px] font-medium\">{node.city}</span>\n          <span className=\"text-muted-foreground ml-auto font-mono text-[11px] uppercase\">\n            {node.id}\n          </span>\n        </div>\n        <div className=\"text-muted-foreground mt-1.5 flex items-center justify-between gap-3 text-[11px]\">\n          <span>{node.status === \"down\" ? \"—\" : `${node.latency} ms`}</span>\n          <span className=\"tabular-nums\">{node.uptime.toFixed(2)}%</span>\n        </div>\n      </MarkerTooltip>\n    </MapMarker>\n  );\n}\n",
      "type": "registry:component",
      "target": "app/uptime-monitor/components/edge-node-marker.tsx"
    },
    {
      "path": "src/registry/blocks/uptime-monitor/components/status-sidebar.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { getNetworkSummary, statusMeta, type EdgeNode } from \"../data\";\n\ninterface StatusSidebarProps {\n  nodes: EdgeNode[];\n}\n\nexport function StatusSidebar({ nodes }: StatusSidebarProps) {\n  const summary = getNetworkSummary(nodes);\n  const summaryMeta = statusMeta[summary.status];\n\n  return (\n    <div className=\"bg-card flex w-56 shrink-0 flex-col overflow-hidden border-r\">\n      <div className=\"border-b p-3\">\n        <p className=\"text-foreground text-sm font-semibold\">Edge Network</p>\n\n        <div className=\"mt-2 flex items-center gap-1.5\">\n          <span className={cn(\"size-2 rounded-full\", summaryMeta.dot)} />\n          <span className={cn(\"text-xs font-medium\", summaryMeta.text)}>\n            {summary.label}\n          </span>\n        </div>\n\n        <div className=\"mt-3 grid grid-cols-2 gap-2\">\n          <div className=\"bg-background/60 rounded-md border p-2\">\n            <p className=\"text-muted-foreground text-[10px] tracking-wide uppercase\">\n              Uptime\n            </p>\n            <p className=\"text-foreground text-sm font-semibold tabular-nums\">\n              {summary.avgUptime.toFixed(2)}%\n            </p>\n          </div>\n          <div className=\"bg-background/60 rounded-md border p-2\">\n            <p className=\"text-muted-foreground text-[10px] tracking-wide uppercase\">\n              Edges up\n            </p>\n            <p className=\"text-foreground text-sm font-semibold tabular-nums\">\n              {summary.operational}\n              <span className=\"text-muted-foreground font-normal\">\n                /{summary.total}\n              </span>\n            </p>\n          </div>\n        </div>\n      </div>\n\n      <ul className=\"flex-1 divide-y overflow-y-auto\">\n        {nodes.map((node) => {\n          const meta = statusMeta[node.status];\n\n          return (\n            <li key={node.id} className=\"flex items-center gap-2.5 px-3 py-2\">\n              <span className={cn(\"size-2 shrink-0 rounded-full\", meta.dot)} />\n\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"text-foreground truncate text-xs font-medium\">\n                  {node.city}\n                </p>\n                <p className=\"text-muted-foreground truncate text-[10px]\">\n                  {node.region}\n                </p>\n              </div>\n\n              <div className=\"flex shrink-0 flex-col items-end\">\n                <span className=\"text-muted-foreground font-mono text-[10px] uppercase\">\n                  {node.id}\n                </span>\n                <span className=\"text-foreground font-mono text-[10px] tabular-nums\">\n                  {node.status === \"down\" ? \"—\" : `${node.latency}ms`}\n                </span>\n              </div>\n            </li>\n          );\n        })}\n      </ul>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "app/uptime-monitor/components/status-sidebar.tsx"
    }
  ],
  "meta": {
    "iframeHeight": "720px"
  },
  "categories": [
    "monitoring",
    "status"
  ],
  "type": "registry:block"
}
