Lattice Grid Buy a licence

demo D311

Raise and clear, held against a flapping feed in Angular

A KPI tile and a grid column that already grade themselves feed an alarm grid on the right: every raise and every clear arrives as its own row. Turn on a flapping reading and watch a hold silence it, a level has to persist before it counts

createAlarms · alarm:raised · alarm:cleared · holdMs

A KPI tile and a grid's own cells already grade a reading as good, warn or critical; the alarms module turns that grading into raise and clear events, fed into an alarm grid on the right so every raise and every clear arrives as its own row. Turn on a flapping reading and a hold silences it: a level has to persist before it counts.

Building…
Loading a live grid…

This is the Angular version. A standalone component takes the whole configuration through one config input, surfaces grid events as outputs, and exposes the live grid on a getter for anything the inputs do not cover.

The configuration

import { Component, effect, signal, viewChild } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid } from '@toclocoinc/lattice-grid';
import { createKPI } from '@toclocoinc/lattice-grid/modules/kpi';
import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';
import { createAlarms } from '@toclocoinc/lattice-grid/modules/alarms';
import { LatticeGridComponent, LatticeKpiComponent, provideLattice } from '@toclocoinc/lattice-grid/angular';

const CPU = { warn: 80, critical: 95, direction: 'lowerIsBetter' };
const HOSTS = [
  { id: 'web-1', host: 'web-1', cpu: 12 },
  { id: 'web-2', host: 'web-2', cpu: 41 },
  { id: 'web-3', host: 'web-3', cpu: 30 },
  { id: 'db-1', host: 'db-1', cpu: 63 },
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [LatticeGridComponent, LatticeKpiComponent],
  template:
    '<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px">' +
    '  <button *ngFor="let ms of holds" (click)="setHold(ms)" [attr.aria-pressed]="ms === holdMs()">Hold {{ ms }}ms</button>' +
    '  <button (click)="toggleFlap()" [attr.aria-pressed]="flapping()">' +
    '    {{ flapping() ? 'Stop the flapping feed' : 'Start the flapping feed' }}' +
    '  </button>' +
    '</div>' +
    '<p>raised {{ raised() }}, cleared {{ cleared() }}</p>' +
    '<div style="display:grid;grid-template-columns:260px minmax(0,1fr) minmax(0,1fr);gap:16px">' +
    '  <lattice-kpi #kpi gridName="hosts" [config]="{ tiles }" style="display:block"></lattice-kpi>' +
    '  <lattice-grid #hosts name="hosts" [config]="hostsConfig()" (grid-ready)="onHostsReady($event)" style="display:block;height:260px"></lattice-grid>' +
    '  <lattice-grid #wall [config]="wallConfig" (grid-ready)="onWallReady($event)" style="display:block;height:260px"></lattice-grid>' +
    '</div>',
})
export class AppComponent {
  kpi = viewChild<LatticeKpiComponent>('kpi');

  holds = [0, 250, 1000];
  holdMs = signal(0);
  flapping = signal(false);
  raised = signal(0);
  cleared = signal(0);

  hostRows = signal(HOSTS);
  hostsConfig = signal({ rowKey: 'id', rows: HOSTS, columns: [
    { field: 'host', title: 'Host', layout: 120 },
    { field: 'cpu', type: 'number', title: 'CPU %', layout: 90 },
  ] });
  wallConfig = { rowKey: 'id', rows: [], columns: [
    { field: 'state', title: 'State', layout: 90 },
    { field: 'level', title: 'Level', layout: 90 },
    { field: 'key', title: 'Key', layout: 120 },
    { field: 'value', title: 'Value', type: 'number', layout: 80 },
  ] };
  tiles = [
    { id: 'peak', label: 'Peak CPU', aggregation: 'max', field: 'cpu', thresholds: CPU },
    { id: 'mean', label: 'Mean CPU', aggregation: 'avg', field: 'cpu', thresholds: CPU },
  ];

  private hostsGrid: any = null;
  private wallGrid: any = null;
  private alarms: any = null;
  private router: any = null;
  private flapTimer: any = null;

  constructor() {
    // Neither the router nor the alarm set has a component of its own: both
    // are ordinary module calls, rebuilt once every piece they read exists
    // and again whenever the hold changes.
    effect((onCleanup) => {
      const hosts = this.hostsGrid;
      const wall = this.wallGrid;
      const kpi = this.kpi()?.instance;
      const holdMs = this.holdMs();
      if (!hosts || !wall || !kpi) return;

      this.router = createDataRouter({ rowKey: 'id', key: 'kind' });
      this.router.attach(wall, 'alarms');
      this.alarms = createAlarms({ holdMs });
      this.alarms.publish(this.router, 'alarms');
      this.alarms.attach(kpi, { sourceId: 'rail' });
      this.alarms.attach(hosts, { sourceId: 'hosts', columns: { cpu: { thresholds: CPU } } });
      this.raised.set(0);
      this.cleared.set(0);
      const offRaised = this.alarms.on('alarm:raised', () => this.raised.update((n: number) => n + 1));
      const offCleared = this.alarms.on('alarm:cleared', () => this.cleared.update((n: number) => n + 1));

      onCleanup(() => {
        offRaised();
        offCleared();
        this.alarms?.destroy();
        this.router?.destroy();
      });
    });
  }

  onHostsReady(grid: any) { this.hostsGrid = grid; }
  onWallReady(grid: any) { this.wallGrid = grid; }

  setHold(ms: number) { this.holdMs.set(ms); }

  toggleFlap() {
    if (this.flapTimer) {
      clearInterval(this.flapTimer);
      this.flapTimer = null;
      this.flapping.set(false);
      this.hostsGrid.rows.apply({ update: [{ id: 'web-3', host: 'web-3', cpu: 30 }] });
      return;
    }
    let high = false;
    this.flapTimer = setInterval(() => {
      high = !high;
      this.hostsGrid.rows.apply({ update: [{ id: 'web-3', host: 'web-3', cpu: high ? 99 : 20 }] });
    }, 60);
    this.flapping.set(true);
  }
}

bootstrapApplication(AppComponent, {
  providers: [provideLattice({ createGrid, createKPI })],
});

A breach that used to paint a colour and say nothing

A KPI tile turning red, or a cell shading amber, tells whoever is looking at the screen right now. It tells nobody else: the on-call rota, a ticket queue, an alarm wall in another room hear nothing, because nothing turned that colour change into an event. Attach the alarms module to a KPI panel, a grid’s own columns, or a Data Router route, and every threshold crossing becomes alarm:raised and alarm:cleared - real events a host can wire to a notification, a ticket, or the alarm grid this demo feeds.

An alarm’s identity is its source, its key and its level, so a reading moving from critical straight to warn clears the critical alarm before it raises the warn one rather than leaving a stale critical open beside a new warn. A source that goes quiet clears whatever was open on it instead of raising anything: silence is not a breach.

A hold that stops a flapping feed paging anyone

Turn on the flapping feed and one host’s CPU reading crosses the critical line roughly every 60 milliseconds. At a zero-millisecond hold that is a fresh alarm several times a second, which is exactly the page nobody wants at 3am for a reading that never actually settled anywhere. Raise the hold to 250ms or 1000ms and the alarm grid goes quiet: a level has to persist for the hold before it is believed, and a crossing back inside the window discards the pending transition rather than delaying it. The reading is still moving on the grid and the KPI tile the whole time; only what counts as a real alarm changes.

How do I raise alarms from a KPI tile or a grid column?

Load the alarms module and call createAlarms({ holdMs }), then attach it to whatever already grades your data: a KPI panel (it listens for the panel’s own tile:status event), a grid (name the columns and their thresholds), or a Data Router route via monitor(). Listen for alarm:raised and alarm:cleared, or feed a grid of your own with alarms.publish(router, 'kind'), which pushes both transitions as rows a Data Router route can drive straight into a table. active() and pending() read the current state back for a summary panel or a test.