Service Health Matrix
blockdevopsStatuspage and BetterStack style global region latency & infrastructure health matrix with overall operational status banner, 6-region latency grid with sparklines, 90-day core service uptime bars, and incident history timeline.
Installation
$pnpm dlx uipkge-ng@latest add service-health-matrix$npx uipkge-ng@latest add service-health-matrix$yarn dlx uipkge-ng@latest add service-health-matrix$bunx uipkge-ng@latest add service-health-matrixManifest:
https://uipkge.dev/r/angular/service-health-matrix.jsonInstalls to:src/app/components/blocks/service-health-matrix/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
RegionHealthinterface RegionHealth {
id: string
name: string
location: string
code: string
latency: number
status: RegionStatus
uptime: number
p95Latency?: number
packetLoss?: number
latencyHistory?: number[]
}ServiceHealthIteminterface ServiceHealthItem {
id: string
name: string
description?: string
icon?: 'auth' | 'cdn' | 'database' | 'webhook' | 'ai'
uptime: number
status: RegionStatus
days?: DayStatus[]
}IncidentUpdateinterface IncidentUpdate {
time: string
status: string
description: string
}IncidentRecordinterface IncidentRecord {
id: string
title: string
date: string
severity: IncidentSeverity
resolved: boolean
duration: string
updates: IncidentUpdate[]
}BannerConfiginterface BannerConfig {
title: string
badge: string
badgeVariant: 'success' | 'warning' | 'destructive' | 'info'
containerClass: string
dotClass: string
textClass: string
}Sparklineinterface Sparkline {
path: string
area: string
lastPoint: { x: number; y: number }
}npm dependencies
Files installed (2)
src/app/components/blocks/service-health-matrix/service-health-matrix.component.ts32.8 kB
// Statuspage / BetterStack style global region latency & infrastructure health matrix: // overall operational status banner, 6-region latency grid with SVG sparklines, 90-day // core service uptime bars, and an incident history timeline. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core' import { Activity, AlertTriangle, Bell, Check, CheckCircle2, Clock, Cpu, Database, Globe, LucideAngularModule, RefreshCw, Server, ShieldCheck, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent, type BadgeVariant } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' export type RegionStatus = 'operational' | 'degraded' | 'outage' | 'maintenance' export type DayStatus = 'up' | 'degraded' | 'down' | 'maintenance' export type IncidentSeverity = 'minor' | 'major' | 'critical' | 'maintenance' export interface RegionHealth { id: string name: string location: string code: string latency: number status: RegionStatus uptime: number p95Latency?: number packetLoss?: number latencyHistory?: number[] } export interface ServiceHealthItem { id: string name: string description?: string icon?: 'auth' | 'cdn' | 'database' | 'webhook' | 'ai' uptime: number status: RegionStatus days?: DayStatus[] } export interface IncidentUpdate { time: string status: string description: string } export interface IncidentRecord { id: string title: string date: string severity: IncidentSeverity resolved: boolean duration: string updates: IncidentUpdate[] } function generate90Days(blips: Array<{ dayAgo: number; status: DayStatus }> = []): DayStatus[] { const days: DayStatus[] = Array.from({ length: 90 }, () => 'up') for (const blip of blips) { const idx = 89 - blip.dayAgo if (idx >= 0 && idx < 90) { days[idx] = blip.status } } return days } const defaultRegions: RegionHealth[] = [ { id: 'us-east', name: 'US East', location: 'N. Virginia', code: 'us-east-1', latency: 24, status: 'operational', uptime: 100, p95Latency: 28, packetLoss: 0.0, latencyHistory: [26, 25, 24, 25, 23, 24, 25, 24, 23, 24, 25, 24], }, { id: 'us-west', name: 'US West', location: 'Oregon', code: 'us-west-2', latency: 38, status: 'operational', uptime: 99.98, p95Latency: 42, packetLoss: 0.0, latencyHistory: [40, 39, 38, 37, 38, 39, 38, 38, 37, 38, 39, 38], }, { id: 'eu-central', name: 'EU Central', location: 'Frankfurt', code: 'eu-central-1', latency: 18, status: 'operational', uptime: 100, p95Latency: 21, packetLoss: 0.0, latencyHistory: [19, 18, 18, 17, 18, 19, 18, 18, 17, 18, 18, 18], }, { id: 'ap-south', name: 'AP South', location: 'Mumbai', code: 'ap-south-1', latency: 45, status: 'operational', uptime: 99.95, p95Latency: 52, packetLoss: 0.01, latencyHistory: [47, 46, 45, 44, 45, 48, 46, 45, 44, 45, 46, 45], }, { id: 'ap-east', name: 'AP East', location: 'Tokyo', code: 'ap-northeast-1', latency: 32, status: 'operational', uptime: 99.99, p95Latency: 36, packetLoss: 0.0, latencyHistory: [34, 33, 32, 31, 32, 33, 32, 32, 31, 32, 33, 32], }, { id: 'sa-east', name: 'SA East', location: 'São Paulo', code: 'sa-east-1', latency: 58, status: 'operational', uptime: 99.91, p95Latency: 65, packetLoss: 0.02, latencyHistory: [62, 60, 59, 58, 61, 58, 59, 58, 57, 58, 60, 58], }, ] const defaultServices: ServiceHealthItem[] = [ { id: 'auth-api', name: 'Authentication API', description: 'OAuth2 / SAML SSO · Session Tokens · JWT Validation', icon: 'auth', uptime: 99.99, status: 'operational', days: generate90Days(), }, { id: 'edge-cdn', name: 'Edge CDN & DNS', description: 'Anycast Global Routing · Edge Cache · SSL Termination', icon: 'cdn', uptime: 100.0, status: 'operational', days: generate90Days(), }, { id: 'postgres-cluster', name: 'Postgres Database Cluster', description: 'Primary Write Node · Regional Read Replicas · Pooler', icon: 'database', uptime: 99.97, status: 'operational', days: generate90Days([{ dayAgo: 42, status: 'degraded' }]), }, { id: 'webhook-dispatcher', name: 'Webhook Dispatcher', description: 'Event Streaming Engine · Exponential Backoff Retries', icon: 'webhook', uptime: 99.98, status: 'operational', days: generate90Days([{ dayAgo: 1, status: 'degraded' }]), }, { id: 'ai-gateway', name: 'AI Inference Gateway', description: 'Model Routing · Streaming Token Buffers · Semantic Cache', icon: 'ai', uptime: 99.95, status: 'operational', days: generate90Days([{ dayAgo: 18, status: 'degraded' }]), }, ] const defaultIncidents: IncidentRecord[] = [ { id: 'inc-1', title: 'Elevated latency on Webhook Dispatcher', date: 'Yesterday · Aug 20, 2026', severity: 'minor', resolved: true, duration: '18 minutes', updates: [ { time: '14:40 UTC', status: 'Resolved', description: 'The backlog of outgoing webhook payloads has fully drained. All queued webhook events were delivered with zero packet drop. Worker autoscaling thresholds have been adjusted.', }, { time: '14:22 UTC', status: 'Investigating', description: 'We identified a localized queue lock in the dispatch workers following a Redis connection pool saturation. Workers were scaled up and connection pool limits raised.', }, ], }, { id: 'inc-2', title: 'Scheduled Maintenance: Postgres Cluster Engine Upgrade', date: 'Aug 14, 2026', severity: 'maintenance', resolved: true, duration: '12 minutes', updates: [ { time: '02:12 UTC', status: 'Completed', description: 'Primary instance switchover completed smoothly. Read replicas synchronized without data lag. All database endpoints are fully operational.', }, { time: '02:00 UTC', status: 'In Progress', description: 'Scheduled rolling restart of secondary read replicas initiated while applying minor database engine updates.', }, ], }, ] interface BannerConfig { title: string badge: string badgeVariant: 'success' | 'warning' | 'destructive' | 'info' containerClass: string dotClass: string textClass: string } const statusBannerConfig: Record<RegionStatus, BannerConfig> = { operational: { title: 'All Systems Operational', badge: '99.99% 90-day uptime', badgeVariant: 'success', containerClass: 'border-success/30 bg-success/10 text-success', dotClass: 'bg-success', textClass: 'text-success', }, degraded: { title: 'Active Service Degradation', badge: 'Elevated Latency Detected', badgeVariant: 'warning', containerClass: 'border-warning/30 bg-warning/10 text-warning', dotClass: 'bg-warning', textClass: 'text-warning', }, outage: { title: 'Partial System Outage', badge: 'Incident Under Investigation', badgeVariant: 'destructive', containerClass: 'border-destructive/30 bg-destructive/10 text-destructive', dotClass: 'bg-destructive', textClass: 'text-destructive', }, maintenance: { title: 'Scheduled Maintenance in Progress', badge: 'Planned Upgrades', badgeVariant: 'info', containerClass: 'border-info/30 bg-info/10 text-info', dotClass: 'bg-info', textClass: 'text-info', }, } const dayClassMap: Record<DayStatus, string> = { up: 'bg-success/80 hover:bg-success bg-success/70 dark:hover:bg-success', degraded: 'bg-warning/90 hover:bg-warning', down: 'bg-destructive hover:bg-destructive', maintenance: 'bg-info hover:bg-info', } const dayLabelMap: Record<DayStatus, string> = { up: 'Operational (100%)', degraded: 'Degraded Performance', down: 'Major Outage', maintenance: 'Scheduled Maintenance', } const severityBadgeMap: Record<IncidentSeverity, { variant: BadgeVariant; label: string }> = { minor: { variant: 'warning', label: 'Minor' }, major: { variant: 'destructive', label: 'Major' }, critical: { variant: 'destructive', label: 'Critical' }, maintenance: { variant: 'secondary', label: 'Maintenance' }, } interface Sparkline { path: string area: string lastPoint: { x: number; y: number } } function buildSparkline(history: number[] = [24, 24, 24, 24]): Sparkline { const min = Math.min(...history) const max = Math.max(...history) const range = max - min || 1 const height = 26 const padding = 3 const points = history.map((val, idx) => { const x = (idx / (history.length - 1)) * 100 const y = height - ((val - min) / range) * (height - padding * 2) - padding return { x, y } }) let path = `M ${points[0].x},${points[0].y}` for (let i = 1; i < points.length; i++) { const prev = points[i - 1] const curr = points[i] const cx = (prev.x + curr.x) / 2 path += ` C ${cx},${prev.y} ${cx},${curr.y} ${curr.x},${curr.y}` } const lastPoint = points[points.length - 1] const area = `${path} L 100,32 L 0,32 Z` return { path, area, lastPoint } } @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-service-health-matrix, [ui-service-health-matrix]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, ], template: ` <div data-slot="service-health-matrix" [class]="rootClass"> <!-- Header Banner --> <div role="status" [class]=" cn( 'flex flex-col gap-4 rounded-xl border p-4 shadow-xs transition-colors sm:p-5 md:flex-row md:items-center md:justify-between', currentBanner().containerClass ) " > <div class="flex items-start gap-3.5 sm:items-center"> <span class="relative mt-1 flex size-3 shrink-0 sm:mt-0"> <span [class]="cn('absolute inline-flex h-full w-full rounded-full opacity-75', currentBanner().dotClass)" ></span> <span [class]="cn('relative inline-flex size-3 rounded-full', currentBanner().dotClass)"></span> </span> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <h1 class="text-foreground text-base leading-none font-semibold tracking-tight sm:text-lg"> {{ currentBanner().title }} </h1> <span [class]="cn('flex items-center gap-2 text-xs font-medium sm:text-sm', currentBanner().textClass)"> {{ systemUptime() }} 90-day uptime </span> </div> <p class="text-muted-foreground text-xs"> Continuous synthetic telemetry and edge probe latency across all global endpoints. </p> </div> </div> <div class="flex flex-wrap items-center gap-2 self-end sm:gap-3 md:self-center"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Clock" class="size-3.5" /> <span>{{ currentUpdatedText() }}</span> <button ui-button variant="ghost" size="icon-sm" class="size-7 rounded-md" aria-label="Refresh status telemetry" (click)="handleRefresh()" > <lucide-icon [img]="RefreshCw" [class]="cn('size-3.5', isRefreshing() && 'animate-spin')" /> </button> </div> <div class="relative"> @if (!isSubscribed()) { <button ui-button variant="outline" size="sm" class="h-8 gap-1.5 text-xs font-medium shadow-xs" (click)="showSubscribeInput.set(!showSubscribeInput())" > <lucide-icon [img]="Bell" class="size-3.5" /> <span>Subscribe to Updates</span> </button> } @else { <span ui-badge variant="success" class="h-8 gap-1.5 px-3 text-xs font-medium"> <lucide-icon [img]="Check" class="size-3.5" /> Subscribed </span> } </div> </div> </div> <!-- Collapsible Subscribe Input Form --> @if (showSubscribeInput() && !isSubscribed()) { <div class="border-border bg-card text-card-foreground flex flex-col items-stretch gap-2 rounded-lg border p-3 shadow-xs sm:flex-row sm:items-center" > <div class="flex-1"> <p class="text-foreground text-xs font-medium">Get instant outage alerts via Email</p> <p class="text-muted-foreground text-xs"> We will only notify you for major severity incidents and maintenance. </p> </div> <div class="flex items-center gap-2"> <input type="email" [value]="emailInput()" (input)="emailInput.set($any($event.target).value)" placeholder="[email protected]" class="border-border bg-background placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border px-2.5 text-xs focus-visible:ring-2 focus-visible:outline-none sm:w-64" (keydown.enter)="handleSubscribeSubmit()" /> <button ui-button size="sm" class="h-8 shrink-0 px-3 text-xs" (click)="handleSubscribeSubmit()"> Subscribe </button> </div> </div> } <!-- Section 1: Global Region Latency Grid --> <div class="space-y-3"> <div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between"> <div> <h2 class="text-foreground text-sm font-semibold tracking-tight">Global Region Latency Grid</h2> <p class="text-muted-foreground text-xs"> Real-time edge response round-trip time and packet loss metrics. </p> </div> <div class="text-muted-foreground flex items-center gap-2 text-xs"> <span class="bg-success inline-flex size-2 rounded-full"></span> <span>All 6 Edge Zones Live</span> </div> </div> <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"> @for (region of regions(); track region.id) { <div ui-card class="border-border/80 bg-card text-card-foreground hover:border-border shadow-xs transition-colors" > <div ui-card-header class="p-4 pb-2"> <div class="flex items-start justify-between gap-2"> <div class="flex items-center gap-2.5"> <div class="bg-muted/70 text-muted-foreground border-border/50 flex size-8 shrink-0 items-center justify-center rounded-md border" > <lucide-icon [img]="Globe" class="size-4" /> </div> <div> <h3 ui-card-title class="text-sm leading-snug font-semibold">{{ region.name }}</h3> <p ui-card-description class="text-xs"> {{ region.location }} · <span class="text-muted-foreground font-mono">{{ region.code }}</span> </p> </div> </div> <span ui-badge [variant]="regionBadgeVariant(region.status)" class="shrink-0 text-xs font-medium capitalize" > <span [class]="cn('mr-1 size-1.5 rounded-full', regionDotClass(region.status))"></span> {{ region.status }} </span> </div> </div> <div ui-card-content class="space-y-3 p-4 pt-1"> <div class="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 pt-1"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground font-mono text-2xl font-bold tracking-tight" >{{ region.latency }}ms</span > <span class="text-muted-foreground text-xs">ping</span> </div> <div [class]="cn('flex items-center gap-1 text-xs font-medium', latencyToneClass(region.latency))"> <lucide-icon [img]="Activity" class="size-3" /> <span>{{ latencyLabel(region.latency) }}</span> </div> </div> <div class="h-9 w-full overflow-hidden pt-1" [attr.aria-label]="region.name + ' latency sparkline'"> <svg class="h-full w-full overflow-visible" viewBox="0 0 100 32" preserveAspectRatio="none"> <defs> <linearGradient [id]="'grad-ng-' + region.id" x1="0" y1="0" x2="0" y2="1"> <stop offset="0%" [attr.stop-color]="sparkColor(region.status)" stop-opacity="0.25" /> <stop offset="100%" [attr.stop-color]="sparkColor(region.status)" stop-opacity="0.0" /> </linearGradient> </defs> <path [attr.d]="spark(region).area" [attr.fill]="'url(#grad-ng-' + region.id + ')'" /> <path [attr.d]="spark(region).path" fill="none" [attr.stroke]="sparkColor(region.status)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> <circle [attr.cx]="spark(region).lastPoint.x" [attr.cy]="spark(region).lastPoint.y" r="2.5" [class]="region.status === 'operational' ? 'fill-success' : 'fill-warning'" /> </svg> </div> <div class="border-border/50 text-muted-foreground flex items-center justify-between border-t pt-2.5 text-xs" > <div class="text-foreground flex items-center gap-1 font-medium"> <lucide-icon [img]="CheckCircle2" class="text-success size-3.5 shrink-0" /> <span>{{ region.uptime }}% uptime</span> </div> <div class="flex items-center gap-2 font-mono"> <span>P95: {{ region.p95Latency ?? 28 }}ms</span> <span>·</span> <span>{{ region.packetLoss ?? 0 }}% loss</span> </div> </div> </div> </div> } </div> </div> <!-- Section 2: Core Services Component Health List --> <div class="space-y-3"> <div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between"> <div> <h2 class="text-foreground text-sm font-semibold tracking-tight">Core Services Component Health</h2> <p class="text-muted-foreground text-xs">90-day daily uptime history and service availability breakdown.</p> </div> <div class="text-muted-foreground flex flex-wrap items-center gap-3 text-xs"> <div class="flex items-center gap-1.5"> <span class="bg-success size-2 rounded-full"></span> <span>Operational</span> </div> <div class="flex items-center gap-1.5"> <span class="bg-warning size-2 rounded-full"></span> <span>Degraded</span> </div> <div class="flex items-center gap-1.5"> <span class="bg-destructive size-2 rounded-full"></span> <span>Outage</span> </div> <div class="flex items-center gap-1.5"> <span class="bg-info size-2 rounded-full"></span> <span>Maintenance</span> </div> </div> </div> <div ui-card class="border-border/80 bg-card text-card-foreground overflow-hidden shadow-xs"> <div class="divide-border/60 divide-y"> @for (service of services(); track service.id) { <div class="hover:bg-muted/15 space-y-3 p-4 transition-colors sm:p-5"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="flex items-center gap-3"> <div class="bg-muted text-muted-foreground border-border/50 flex size-8 shrink-0 items-center justify-center rounded-lg border" > @switch (service.icon) { @case ('auth') { <lucide-icon [img]="ShieldCheck" class="size-4" /> } @case ('cdn') { <lucide-icon [img]="Globe" class="size-4" /> } @case ('database') { <lucide-icon [img]="Database" class="size-4" /> } @case ('webhook') { <lucide-icon [img]="Zap" class="size-4" /> } @case ('ai') { <lucide-icon [img]="Cpu" class="size-4" /> } @default { <lucide-icon [img]="Server" class="size-4" /> } } </div> <div> <p class="text-foreground text-sm leading-tight font-semibold">{{ service.name }}</p> @if (service.description) { <p class="text-muted-foreground mt-0.5 text-xs">{{ service.description }}</p> } </div> </div> <div class="flex items-center gap-3 self-start sm:self-auto"> <span class="text-foreground font-mono text-xs font-semibold tabular-nums"> {{ service.uptime }}% uptime </span> <span ui-badge [variant]="serviceBadgeVariant(service.status)" class="text-xs font-medium capitalize" > <span [class]="cn('mr-1 size-1.5 rounded-full', regionDotClass(service.status))"></span> {{ service.status }} </span> </div> </div> <div class="space-y-1.5"> <div class="flex w-full gap-px overflow-hidden sm:gap-[2px]" role="img" [attr.aria-label]="service.name + ' 90-day availability history (' + service.uptime + '%)'" > @for (day of daysFor(service); track $index) { <span [class]=" cn( 'h-6 min-w-px flex-1 cursor-pointer rounded-xs transition-transform hover:scale-y-125', dayClass(day) ) " [attr.title]="'Day ' + (90 - $index) + ' ago: ' + dayLabel(day)" ></span> } </div> <div class="text-muted-foreground flex items-center justify-between pt-0.5 text-xs"> <span>90 days ago</span> <span class="hidden sm:inline">100% daily health check baseline</span> <span>Today</span> </div> </div> </div> } </div> </div> </div> <!-- Section 3: Past Incidents Timeline --> <div class="space-y-3"> <div> <h2 class="text-foreground text-sm font-semibold tracking-tight">Past Incidents & Maintenance</h2> <p class="text-muted-foreground text-xs">Detailed post-mortems and scheduled infrastructure updates.</p> </div> <div ui-card class="border-border/80 bg-card text-card-foreground p-5 shadow-xs sm:p-6"> @if (incidents().length > 0) { <div class="space-y-8"> @for (incident of incidents(); track incident.id) { <div class="border-border/80 relative space-y-3 border-l-2 pb-2 pl-6 last:border-l-transparent sm:pl-8"> <div [class]=" cn( 'bg-background absolute top-0 -left-[11px] flex size-5 items-center justify-center rounded-full border', incident.resolved ? 'border-success text-success' : 'border-warning text-warning' ) " > @if (incident.resolved) { <lucide-icon [img]="Check" class="size-3" /> } @else { <lucide-icon [img]="AlertTriangle" class="size-3" /> } </div> <div class="flex flex-col gap-1.5 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <h3 class="text-foreground text-sm font-semibold">{{ incident.title }}</h3> <span ui-badge [variant]="incident.resolved ? 'success' : 'warning'" class="text-xs"> {{ incident.resolved ? 'Resolved' : 'Ongoing' }} </span> <span ui-badge [variant]="severityBadge(incident.severity).variant" class="text-xs"> {{ severityBadge(incident.severity).label }} </span> </div> <p class="text-muted-foreground text-xs">{{ incident.date }}</p> </div> <span class="text-muted-foreground font-mono text-xs whitespace-nowrap"> Duration: {{ incident.duration }} </span> </div> <div class="bg-muted/40 border-border/60 space-y-3 rounded-lg border p-3.5 text-xs sm:p-4"> @for (update of incident.updates; track $index) { <div class="border-border/40 space-y-1 border-b pb-2.5 last:border-b-0 last:pb-0"> <div class="text-foreground flex items-center gap-2 font-medium"> <span class="text-muted-foreground font-mono">{{ update.time }}</span> <span>·</span> <span [class]=" update.status === 'Resolved' || update.status === 'Completed' ? 'text-success' : 'text-foreground' " > {{ update.status }} </span> </div> <p class="text-muted-foreground pl-0 leading-relaxed sm:pl-2"> {{ update.description }} </p> </div> } </div> </div> } </div> } @else { <div class="flex flex-col items-center justify-center py-8 text-center"> <lucide-icon [img]="CheckCircle2" class="text-success mb-2 size-8" /> <p class="text-foreground text-sm font-medium">No incidents reported</p> <p class="text-muted-foreground mt-0.5 text-xs"> All services have maintained 100% operational integrity. </p> </div> } </div> </div> </div> `, }) export class UiServiceHealthMatrixComponent { protected readonly cn = cn protected readonly Activity = Activity protected readonly AlertTriangle = AlertTriangle protected readonly Bell = Bell protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly Cpu = Cpu protected readonly Database = Database protected readonly Globe = Globe protected readonly RefreshCw = RefreshCw protected readonly Server = Server protected readonly ShieldCheck = ShieldCheck protected readonly Zap = Zap @Input('systemStatus') set systemStatusInput(v: RegionStatus | undefined) { if (v) this.systemStatus.set(v) } @Input('systemUptime') set systemUptimeInput(v: string | undefined) { if (v !== undefined) this.systemUptime.set(v) } @Input('lastUpdated') set lastUpdatedInput(v: string | undefined) { if (v !== undefined) { this.lastUpdated.set(v) this.currentUpdatedText.set(v) } } @Input('regions') set regionsInput(v: RegionHealth[] | undefined) { if (v) this.regions.set(v.map((r) => ({ ...r, latencyHistory: r.latencyHistory ? [...r.latencyHistory] : undefined }))) } @Input('services') set servicesInput(v: ServiceHealthItem[] | undefined) { if (v) this.services.set(v.map((s) => ({ ...s, days: s.days ? [...s.days] : undefined }))) } @Input('incidents') set incidentsInput(v: IncidentRecord[] | undefined) { if (v) this.incidents.set(v.map((i) => ({ ...i, updates: i.updates.map((u) => ({ ...u })) }))) } @Input('class') className?: string readonly systemStatus = signal<RegionStatus>('operational') readonly systemUptime = signal('99.99%') readonly lastUpdated = signal('Updated 30s ago') readonly regions = signal<RegionHealth[]>( defaultRegions.map((r) => ({ ...r, latencyHistory: [...(r.latencyHistory ?? [])] })), ) readonly services = signal<ServiceHealthItem[]>(defaultServices.map((s) => ({ ...s, days: [...(s.days ?? [])] }))) readonly incidents = signal<IncidentRecord[]>( defaultIncidents.map((i) => ({ ...i, updates: i.updates.map((u) => ({ ...u })) })), ) readonly isRefreshing = signal(false) readonly isSubscribed = signal(false) readonly showSubscribeInput = signal(false) readonly emailInput = signal('') readonly currentUpdatedText = signal('Updated 30s ago') private refreshTimer: ReturnType<typeof setTimeout> | undefined constructor() { inject(DestroyRef).onDestroy(() => clearTimeout(this.refreshTimer)) } readonly currentBanner = computed<BannerConfig>( () => statusBannerConfig[this.systemStatus()] ?? statusBannerConfig.operational, ) get rootClass(): string { return cn('w-full space-y-6', this.className) } handleRefresh(): void { this.isRefreshing.set(true) clearTimeout(this.refreshTimer) this.refreshTimer = setTimeout(() => { this.isRefreshing.set(false) this.currentUpdatedText.set('Updated just now') }, 400) } handleSubscribeSubmit(): void { if (this.emailInput().trim().length > 0) { this.isSubscribed.set(true) this.showSubscribeInput.set(false) this.emailInput.set('') } } daysFor(service: ServiceHealthItem): DayStatus[] { return service.days ?? generate90Days() } dayClass(day: DayStatus): string { return dayClassMap[day] } dayLabel(day: DayStatus): string { return dayLabelMap[day] } severityBadge(severity: IncidentSeverity): { variant: BadgeVariant; label: string } { return severityBadgeMap[severity] } spark(region: RegionHealth): Sparkline { return buildSparkline(region.latencyHistory) } sparkColor(status: RegionStatus): string { return status === 'operational' ? 'var(--color-emerald-500, #10b981)' : 'var(--color-amber-500, #f59e0b)' } regionBadgeVariant(status: RegionStatus): BadgeVariant { return status === 'operational' ? 'success' : status === 'degraded' ? 'warning' : 'destructive' } serviceBadgeVariant(status: RegionStatus): BadgeVariant { return status === 'operational' ? 'success' : status === 'degraded' ? 'warning' : 'destructive' } regionDotClass(status: RegionStatus): string { return status === 'operational' ? 'bg-success' : status === 'degraded' ? 'bg-warning' : 'bg-destructive' } latencyToneClass(latency: number): string { return latency < 30 ? 'text-success' : latency < 50 ? 'text-info' : 'text-warning' } latencyLabel(latency: number): string { return latency < 30 ? 'Optimal' : latency < 50 ? 'Normal' : 'Elevated' } }src/app/components/blocks/service-health-matrix/index.ts0.2 kB
export { UiServiceHealthMatrixComponent, type RegionStatus, type DayStatus, type IncidentSeverity, type RegionHealth, type ServiceHealthItem, type IncidentUpdate, type IncidentRecord, } from './service-health-matrix.component'
Raw manifest:https://uipkge.dev/r/angular/service-health-matrix.json