Lattice Grid Buy a licence

demo D311

Raise and clear, held against a flapping feed in ESM

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 ES module version. The grid is imported straight from a CDN as a module, the same configuration as the vanilla tab without a global script tag, so it drops into any bundler or a bare module script.

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.min.css">

<div id="tiles" style="border: 1px solid #e6e6e6; border-radius: 9px; padding: 12px; width: 260px"></div>
<div style="display: flex; gap: 16px; margin-top: 12px">
  <div id="hosts" style="flex: 1; height: 260px"></div>
  <div id="wall" style="flex: 1; height: 260px"></div>
</div>
<div style="margin-top: 10px">
  <button id="hold-0" type="button" aria-pressed="true">Hold 0ms</button>
  <button id="hold-250" type="button" aria-pressed="false">Hold 250ms</button>
  <button id="flap" type="button" aria-pressed="false">Start the flapping feed</button>
</div>

<script type="module">
  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';

  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 },
  ];

  // The hosts grid grades its own cpu column; the KPI rail grades the same
  // rows a second way. Neither one is the alarms module yet - both are
  // ordinary configuration the module reads from below.
  const grid = createGrid(document.getElementById('hosts'), {
    rowKey: 'id',
    rows: HOSTS,
    columns: [
      { field: 'host', title: 'Host', layout: 120 },
      { field: 'cpu', type: 'number', title: 'CPU %', layout: 90 },
    ],
  });
  const kpi = createKPI(document.getElementById('tiles'), {
    grid,
    tiles: [
      { id: 'peak', label: 'Peak CPU', aggregation: 'max', field: 'cpu', thresholds: CPU },
      { id: 'mean', label: 'Mean CPU', aggregation: 'avg', field: 'cpu', thresholds: CPU },
    ],
  });

  // The router carries the alarm feed, partitioned on 'kind' - the field
  // alarms.publish() stamps on every row it pushes.
  const wall = createGrid(document.getElementById('wall'), {
    rowKey: 'id',
    rows: [],
    columns: [
      { field: 'state', title: 'State', layout: 90 },
      { field: 'level', title: 'Level', layout: 90 },
      { field: 'key', title: 'Key', layout: 120 },
      { field: 'value', type: 'number', title: 'Value', layout: 80 },
    ],
  });
  const router = createDataRouter({ rowKey: 'id', key: 'kind' });
  router.attach(wall, 'alarms');

  // Rebuilt rather than mutated: holdMs is read when a source is attached,
  // which is also the honest demonstration of what the option does.
  let alarms;
  function rebuild(holdMs) {
    alarms?.destroy();
    alarms = createAlarms({ holdMs });
    alarms.publish(router, 'alarms');
    alarms.attach(kpi, { sourceId: 'rail' });
    alarms.attach(grid, { sourceId: 'hosts', columns: { cpu: { thresholds: CPU } } });
  }
  rebuild(0);

  document.getElementById('hold-0').addEventListener('click', (e) => {
    e.target.setAttribute('aria-pressed', 'true');
    document.getElementById('hold-250').setAttribute('aria-pressed', 'false');
    rebuild(0);
  });
  document.getElementById('hold-250').addEventListener('click', (e) => {
    e.target.setAttribute('aria-pressed', 'true');
    document.getElementById('hold-0').setAttribute('aria-pressed', 'false');
    rebuild(250);
  });

  // web-3 crosses the critical cut roughly every 60ms while this runs. At a
  // 0ms hold that is a fresh alarm several times a second; raise the hold
  // above and the alarm grid goes quiet, because a level now has to persist
  // for the hold before it counts.
  let flapping = null;
  document.getElementById('flap').addEventListener('click', (e) => {
    if (flapping) {
      clearInterval(flapping);
      flapping = null;
      e.target.setAttribute('aria-pressed', 'false');
      e.target.textContent = 'Start the flapping feed';
      grid.rows.apply({ update: [{ id: 'web-3', host: 'web-3', cpu: 30 }] });
      return;
    }
    let high = false;
    flapping = setInterval(() => {
      high = !high;
      grid.rows.apply({ update: [{ id: 'web-3', host: 'web-3', cpu: high ? 99 : 20 }] });
    }, 60);
    e.target.setAttribute('aria-pressed', 'true');
    e.target.textContent = 'Stop the flapping feed';
  });
</script>

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.