Menu

Change language

GitHub repo

Usage Metering Dashboard

blockfinance

Real-time consumption metering and overage monitoring dashboard: billing cycle tracking with days remaining, 4 key resource meter cards (API requests, compute hours, vector storage, bandwidth) with color-shifting threshold progress bars and projected month-end usage, projected invoice cost summary with overage breakdown, auto-scale overage protection toggle, email alert threshold selector, and recent usage events audit log.

Installation

$npx uipkge-ng@latest add usage-metering-dashboard
Manifest:https://uipkge.dev/r/angular/usage-metering-dashboard.jsonInstalls to:src/app/components/blocks/usage-metering-dashboard/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

UsageEvent
interface UsageEvent {
  id: string
  timestamp: string
  service: string
  description: string
  volume: string
  status: 'warning' | 'normal'
  statusLabel: string
}

Files installed (2)

  • src/app/components/blocks/usage-metering-dashboard/usage-metering-dashboard.component.ts22.5 kB
    // Real-time consumption metering and overage monitoring dashboard: billing cycle
    // tracking with days remaining, 4 resource meter cards with threshold progress bars and
    // projected month-end usage, projected invoice cost summary with overage breakdown,
    // auto-scale overage protection toggle, email alert threshold selector, and recent
    // usage events audit log. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core'
    import {
      Activity,
      ArrowDownUp,
      ArrowUpRight,
      Cpu,
      Database,
      LucideAngularModule,
      TriangleAlert,
      Zap,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiProgressComponent } from '@/ui/progress/progress.component'
    import {
      UiSelectComponent,
      UiSelectContentComponent,
      UiSelectItemComponent,
      UiSelectTriggerComponent,
      UiSelectValueComponent,
    } from '@/ui/select/select.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import { UiSwitchComponent } from '@/ui/switch/switch.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export interface UsageEvent {
      id: string
      timestamp: string
      service: string
      description: string
      volume: string
      status: 'warning' | 'normal'
      statusLabel: string
    }
    
    const events: UsageEvent[] = [
      {
        id: 'evt-1',
        timestamp: 'Aug 18, 2026 14:22',
        service: 'API Gateway',
        description: 'Spike: Batch sync job',
        volume: '+240,000 reqs',
        status: 'warning',
        statusLabel: 'High Spike',
      },
      {
        id: 'evt-2',
        timestamp: 'Aug 17, 2026 09:15',
        service: 'Vector Storage',
        description: 'Index re-indexing & optimization',
        volume: '+12.4 GB',
        status: 'normal',
        statusLabel: 'Normal',
      },
      {
        id: 'evt-3',
        timestamp: 'Aug 16, 2026 22:40',
        service: 'Compute Cluster',
        description: 'Model inference burst job',
        volume: '+85.0 hrs',
        status: 'normal',
        statusLabel: 'Normal',
      },
      {
        id: 'evt-4',
        timestamp: 'Aug 15, 2026 11:05',
        service: 'CDN Edge',
        description: 'Media cache invalidation & egress',
        volume: '+64.2 GB',
        status: 'normal',
        statusLabel: 'Normal',
      },
      {
        id: 'evt-5',
        timestamp: 'Aug 14, 2026 18:30',
        service: 'API Gateway',
        description: 'Webhook delivery retry storm',
        volume: '+180,000 reqs',
        status: 'warning',
        statusLabel: 'High Spike',
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-usage-metering-dashboard, [ui-usage-metering-dashboard]',
      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,
        UiProgressComponent,
        UiSelectComponent,
        UiSelectContentComponent,
        UiSelectItemComponent,
        UiSelectTriggerComponent,
        UiSelectValueComponent,
        UiSeparatorComponent,
        UiSwitchComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <div data-slot="usage-metering-dashboard" [class]="rootClass">
          <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
            <div class="space-y-1">
              <h1 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl">Usage & Metering</h1>
              <p class="text-muted-foreground text-sm">
                Monitor your organization's resource consumption and billing cycle limits.
              </p>
            </div>
            <div class="flex items-center gap-2">
              <button ui-button variant="outline" size="sm" class="shrink-0 gap-1.5">
                <lucide-icon [img]="Zap" class="fill-warning text-warning size-4" />
                <span>Change Plan</span>
                <lucide-icon [img]="ArrowUpRight" class="text-muted-foreground size-3.5" />
              </button>
            </div>
          </div>
    
          <div ui-card class="bg-muted/30 p-4">
            <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
              <div class="space-y-1">
                <div class="flex items-center gap-2">
                  <span class="text-foreground text-sm font-semibold">Current Billing Period</span>
                  <span ui-badge variant="outline" class="text-xs font-normal">Scale Plan — $199/mo</span>
                </div>
                <p class="text-muted-foreground text-xs tabular-nums">
                  Day 18 of 30 · Current period ends Sep 1, 2026 (12 days remaining)
                </p>
              </div>
              <div class="w-full space-y-1.5 sm:w-72">
                <div class="flex items-center justify-between gap-x-2 text-xs">
                  <span class="text-muted-foreground">Cycle Progress</span>
                  <span class="text-foreground font-medium tabular-nums">60%</span>
                </div>
                <ui-progress [value]="60" class="h-1.5" />
              </div>
            </div>
          </div>
    
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <div ui-card class="flex flex-col justify-between">
              <div ui-card-header class="pb-3">
                <div class="flex items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2.5">
                    <div
                      class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="Activity" class="size-4" />
                    </div>
                    <h3 ui-card-title class="truncate text-sm font-medium">API Requests</h3>
                  </div>
                  <span ui-badge variant="warning" class="shrink-0 gap-1 text-xs">
                    <lucide-icon [img]="TriangleAlert" class="size-3" />
                    <span>Approaching Limit</span>
                  </span>
                </div>
              </div>
              <div ui-card-content class="space-y-3">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 text-xs">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">1.84M</span>
                    <span class="text-muted-foreground tabular-nums">/ 2.0M limit</span>
                  </div>
                  <ui-progress [value]="92" class="[&_[data-slot=progress-indicator]]:bg-warning h-2" />
                  <div class="mt-1.5 flex items-center justify-between gap-x-2 text-xs">
                    <span class="text-muted-foreground tabular-nums">92% utilized</span>
                    <span class="text-warning font-medium tabular-nums">160K remaining</span>
                  </div>
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="flex items-center justify-between gap-x-2">
                    <span class="text-muted-foreground">Projected at month-end:</span>
                    <span class="text-foreground font-medium tabular-nums">2.15M</span>
                  </div>
                  <p class="text-warning mt-0.5 text-xs font-medium tabular-nums">+150K overage (~$15.00)</p>
                </div>
              </div>
            </div>
    
            <div ui-card class="flex flex-col justify-between">
              <div ui-card-header class="pb-3">
                <div class="flex items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2.5">
                    <div
                      class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="Cpu" class="size-4" />
                    </div>
                    <h3 ui-card-title class="truncate text-sm font-medium">Compute Hours</h3>
                  </div>
                  <span ui-badge variant="success" class="shrink-0 text-xs">Healthy</span>
                </div>
              </div>
              <div ui-card-content class="space-y-3">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 text-xs">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">420</span>
                    <span class="text-muted-foreground tabular-nums">/ 1,000 hrs limit</span>
                  </div>
                  <ui-progress [value]="42" class="[&_[data-slot=progress-indicator]]:bg-success h-2" />
                  <div class="mt-1.5 flex items-center justify-between gap-x-2 text-xs">
                    <span class="text-muted-foreground tabular-nums">42% utilized</span>
                    <span class="text-muted-foreground tabular-nums">580 hrs remaining</span>
                  </div>
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="flex items-center justify-between gap-x-2">
                    <span class="text-muted-foreground">Projected at month-end:</span>
                    <span class="text-foreground font-medium tabular-nums">700 hrs</span>
                  </div>
                  <p class="text-muted-foreground mt-0.5 text-xs">Within allowance (300 hrs headroom)</p>
                </div>
              </div>
            </div>
    
            <div ui-card class="flex flex-col justify-between">
              <div ui-card-header class="pb-3">
                <div class="flex items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2.5">
                    <div
                      class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="Database" class="size-4" />
                    </div>
                    <h3 ui-card-title class="truncate text-sm font-medium">Vector Storage</h3>
                  </div>
                  <span ui-badge variant="success" class="shrink-0 text-xs">Healthy</span>
                </div>
              </div>
              <div ui-card-content class="space-y-3">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 text-xs">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">78 GB</span>
                    <span class="text-muted-foreground tabular-nums">/ 100 GB limit</span>
                  </div>
                  <ui-progress [value]="78" class="h-2" />
                  <div class="mt-1.5 flex items-center justify-between gap-x-2 text-xs">
                    <span class="text-muted-foreground tabular-nums">78% utilized</span>
                    <span class="text-muted-foreground tabular-nums">22 GB remaining</span>
                  </div>
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="flex items-center justify-between gap-x-2">
                    <span class="text-muted-foreground">Projected at month-end:</span>
                    <span class="text-foreground font-medium tabular-nums">84 GB</span>
                  </div>
                  <p class="text-muted-foreground mt-0.5 text-xs">Within allowance (16 GB headroom)</p>
                </div>
              </div>
            </div>
    
            <div ui-card class="flex flex-col justify-between">
              <div ui-card-header class="pb-3">
                <div class="flex items-center justify-between gap-2">
                  <div class="flex min-w-0 items-center gap-2.5">
                    <div
                      class="bg-muted text-muted-foreground border-border flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs"
                      aria-hidden="true"
                    >
                      <lucide-icon [img]="ArrowDownUp" class="size-4" />
                    </div>
                    <h3 ui-card-title class="truncate text-sm font-medium">Bandwidth / Egress</h3>
                  </div>
                  <span ui-badge variant="success" class="shrink-0 text-xs">Healthy</span>
                </div>
              </div>
              <div ui-card-content class="space-y-3">
                <div>
                  <div class="mb-1 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 text-xs">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">412 GB</span>
                    <span class="text-muted-foreground tabular-nums">/ 500 GB limit</span>
                  </div>
                  <ui-progress [value]="82.4" class="h-2" />
                  <div class="mt-1.5 flex items-center justify-between gap-x-2 text-xs">
                    <span class="text-muted-foreground tabular-nums">82.4% utilized</span>
                    <span class="text-muted-foreground tabular-nums">88 GB remaining</span>
                  </div>
                </div>
                <div class="border-border/60 border-t pt-2.5 text-xs">
                  <div class="flex items-center justify-between gap-x-2">
                    <span class="text-muted-foreground">Projected at month-end:</span>
                    <span class="text-foreground font-medium tabular-nums">485 GB</span>
                  </div>
                  <p class="text-muted-foreground mt-0.5 text-xs">Within allowance (15 GB headroom)</p>
                </div>
              </div>
            </div>
          </div>
    
          <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
            <div ui-card>
              <div ui-card-header>
                <div class="flex items-center justify-between gap-x-2">
                  <h3 ui-card-title>Projected Month-End Cost</h3>
                  <span ui-badge variant="warning" class="gap-1 text-xs">
                    <lucide-icon [img]="TriangleAlert" class="size-3" />
                    <span>Overage Projected</span>
                  </span>
                </div>
                <p ui-card-description>Estimated cycle total factoring base tier and current consumption trajectory.</p>
              </div>
              <div ui-card-content class="space-y-4">
                <div
                  class="border-border bg-muted/40 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 rounded-lg border p-4"
                >
                  <div>
                    <p class="text-muted-foreground text-xs font-medium tracking-wider uppercase">Projected Invoice</p>
                    <p class="text-foreground text-3xl font-bold tracking-tight tabular-nums">$214.00</p>
                  </div>
                  <div class="text-right">
                    <span class="text-muted-foreground text-xs">
                      Base Plan: <strong class="text-foreground font-semibold tabular-nums">$199.00</strong>
                    </span>
                    <p class="text-warning text-xs font-semibold tabular-nums">+$15.00 overage</p>
                  </div>
                </div>
    
                <div class="space-y-2.5 text-sm">
                  <div class="flex items-center justify-between gap-x-2">
                    <span class="text-foreground">Scale Plan Base Subscription</span>
                    <span class="text-foreground font-medium tabular-nums">$199.00</span>
                  </div>
                  <div class="flex items-center justify-between gap-x-2">
                    <div class="flex items-center gap-1.5">
                      <span class="text-foreground">API Requests Overage</span>
                      <span ui-badge variant="outline" class="text-xs font-normal">150K @ $0.10 / 1K</span>
                    </div>
                    <span class="text-warning font-medium tabular-nums">+$15.00</span>
                  </div>
                  <div class="flex items-center justify-between gap-x-2">
                    <span class="text-muted-foreground">Compute Hours (1,000 hrs allowance)</span>
                    <span class="text-muted-foreground tabular-nums">$0.00</span>
                  </div>
                  <div class="flex items-center justify-between gap-x-2">
                    <span class="text-muted-foreground">Storage & Egress Bandwidth</span>
                    <span class="text-muted-foreground tabular-nums">$0.00</span>
                  </div>
                </div>
    
                <div ui-separator></div>
    
                <div class="text-muted-foreground flex items-center justify-between gap-x-2 text-xs">
                  <span>Next scheduled invoice date</span>
                  <span class="text-foreground font-medium tabular-nums">September 1, 2026</span>
                </div>
              </div>
            </div>
    
            <div ui-card>
              <div ui-card-header>
                <h3 ui-card-title>Overage Protection & Alerts</h3>
                <p ui-card-description>Configure notifications and automated guardrails before limits are breached.</p>
              </div>
              <div ui-card-content class="space-y-4">
                <div class="flex items-start justify-between gap-4">
                  <div class="space-y-1">
                    <label class="text-foreground text-sm leading-none font-medium">Auto-scale Overage Protection</label>
                    <p class="text-muted-foreground text-xs leading-relaxed">
                      Automatically throttle or scale resources to prevent unexpected billing spikes beyond set allowances.
                    </p>
                  </div>
                  <ui-switch
                    [checked]="autoScaleProtection()"
                    (checkedChange)="autoScaleProtection.set($event)"
                    aria-label="Toggle auto-scale overage protection"
                  />
                </div>
    
                <div ui-separator></div>
    
                <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                  <div class="space-y-1">
                    <label class="text-foreground text-sm leading-none font-medium">Email Alert Threshold</label>
                    <p class="text-muted-foreground text-xs">
                      Notify workspace admins when any metric crosses this percentage.
                    </p>
                  </div>
                  <ui-select [value]="alertThreshold()" (valueChange)="alertThreshold.set($event)">
                    <button ui-select-trigger class="w-full text-xs sm:w-44" aria-label="Email alert threshold">
                      <ui-select-value placeholder="Select threshold" />
                    </button>
                    <ui-select-content>
                      <ui-select-item value="80">80% of limit</ui-select-item>
                      <ui-select-item value="90">90% of limit</ui-select-item>
                      <ui-select-item value="100">100% of limit</ui-select-item>
                    </ui-select-content>
                  </ui-select>
                </div>
    
                <div ui-separator></div>
    
                <div class="bg-muted/40 border-border space-y-1.5 rounded-lg border p-3">
                  <div class="flex items-center justify-between gap-x-2 text-xs">
                    <span class="text-muted-foreground">Hard Spending Cap</span>
                    <span class="text-foreground font-semibold tabular-nums">$300.00 / mo</span>
                  </div>
                  <div class="flex items-center justify-between gap-x-2 text-xs">
                    <span class="text-muted-foreground">Recipients</span>
                    <span class="text-muted-foreground truncate">[email protected]</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
    
          <div ui-card>
            <div ui-card-header>
              <div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
                <div>
                  <h3 ui-card-title>Recent Usage Events</h3>
                  <p ui-card-description>
                    High-volume consumption spikes and automated resource scaling events this cycle.
                  </p>
                </div>
                <span ui-badge variant="outline" class="w-fit text-xs font-normal tabular-nums">5 events logged</span>
              </div>
            </div>
            <div ui-card-content class="p-0 sm:p-6 sm:pt-0">
              <div class="overflow-x-auto">
                <table ui-table>
                  <thead ui-table-header>
                    <tr ui-table-row>
                      <th ui-table-head>Timestamp</th>
                      <th ui-table-head>Service</th>
                      <th ui-table-head>Event Description</th>
                      <th ui-table-head class="text-right">Volume</th>
                      <th ui-table-head class="text-right">Status</th>
                    </tr>
                  </thead>
                  <tbody ui-table-body>
                    @for (event of usageEvents; track event.id) {
                      <tr ui-table-row>
                        <td ui-table-cell class="text-muted-foreground text-xs whitespace-nowrap tabular-nums">
                          {{ event.timestamp }}
                        </td>
                        <td ui-table-cell class="text-foreground text-xs font-medium whitespace-nowrap">
                          {{ event.service }}
                        </td>
                        <td ui-table-cell class="text-muted-foreground text-xs">{{ event.description }}</td>
                        <td
                          ui-table-cell
                          class="text-foreground text-right text-xs font-medium whitespace-nowrap tabular-nums"
                        >
                          {{ event.volume }}
                        </td>
                        <td ui-table-cell class="text-right">
                          <span
                            ui-badge
                            [variant]="event.status === 'warning' ? 'warning' : 'success'"
                            class="text-xs whitespace-normal"
                          >
                            {{ event.statusLabel }}
                          </span>
                        </td>
                      </tr>
                    }
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiUsageMeteringDashboardComponent {
      protected readonly Activity = Activity
      protected readonly ArrowDownUp = ArrowDownUp
      protected readonly ArrowUpRight = ArrowUpRight
      protected readonly Cpu = Cpu
      protected readonly Database = Database
      protected readonly TriangleAlert = TriangleAlert
      protected readonly Zap = Zap
    
      protected readonly usageEvents = events
    
      @Input('class') className?: string
      @Input() set initialAutoScaleProtection(v: boolean | undefined) {
        if (v !== undefined) this.autoScaleProtection.set(v)
      }
      @Input() set initialAlertThreshold(v: string | undefined) {
        if (v) this.alertThreshold.set(v)
      }
    
      readonly autoScaleProtection = signal(true)
      readonly alertThreshold = signal('90')
    
      get rootClass(): string {
        return cn('mx-auto w-full max-w-6xl space-y-6', this.className)
      }
    }
    
  • src/app/components/blocks/usage-metering-dashboard/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/usage-metering-dashboard.json