Lattice Grid Buy a licence

demo D311

Raise and clear, held against a flapping feed in React

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 React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.

The configuration

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

<div id="app"></div>

<script type="module">
  import React from 'https://esm.sh/react@18';
  import { createRoot } from 'https://esm.sh/react-dom@18/client';
  import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.esm.min.js';
  import { createKPI } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/kpi.esm.min.js';
  import { createDataRouter } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/data-router.esm.min.js';
  import { createAlarms } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/alarms.esm.min.js';
  import { createLatticeReact } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/react.esm.min.js';

  // One viewer component per shipped viewer; alarms has no view of its own,
  // so it stays an imperative companion, wired once the grids and the KPI
  // panel it reads already exist.
  const L = createLatticeReact({ React, createGrid, createKPI });

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

  function App() {
    const [hostRows, setHostRows] = React.useState(HOSTS);
    const [holdMs, setHoldMs] = React.useState(0);
    const [flapping, setFlapping] = React.useState(false);
    const [tally, setTally] = React.useState({ raised: 0, cleared: 0 });
    const [hostsGrid, setHostsGrid] = React.useState(null);
    const [wallGrid, setWallGrid] = React.useState(null);
    const kpiRef = React.useRef(null);
    const flapTimer = React.useRef(null);

    // The router and the alarm set are ordinary module calls, rebuilt
    // whenever the hold changes - which is also the honest demonstration of
    // what the option does - and torn down with the effect that built them.
    React.useEffect(() => {
      if (!hostsGrid || !wallGrid || !kpiRef.current?.instance) return undefined;
      const router = createDataRouter({ rowKey: 'id', key: 'kind' });
      router.attach(wallGrid, 'alarms');
      const alarms = createAlarms({ holdMs });
      alarms.publish(router, 'alarms');
      alarms.attach(kpiRef.current.instance, { sourceId: 'rail' });
      alarms.attach(hostsGrid, { sourceId: 'hosts', columns: { cpu: { thresholds: CPU } } });
      setTally({ raised: 0, cleared: 0 });
      const offRaised = alarms.on('alarm:raised', () => setTally((t) => ({ ...t, raised: t.raised + 1 })));
      const offCleared = alarms.on('alarm:cleared', () => setTally((t) => ({ ...t, cleared: t.cleared + 1 })));
      return () => {
        offRaised();
        offCleared();
        alarms.destroy();
        router.destroy();
      };
    }, [hostsGrid, wallGrid, holdMs]);

    React.useEffect(() => () => { if (flapTimer.current) clearInterval(flapTimer.current); }, []);

    const toggleFlap = () => {
      if (flapTimer.current) {
        clearInterval(flapTimer.current);
        flapTimer.current = null;
        setFlapping(false);
        setHostRows((rows) => rows.map((h) => (h.id === 'web-3' ? { ...h, cpu: 30 } : h)));
        return;
      }
      let high = false;
      flapTimer.current = setInterval(() => {
        high = !high;
        setHostRows((rows) => rows.map((h) => (h.id === 'web-3' ? { ...h, cpu: high ? 99 : 20 } : h)));
      }, 60);
      setFlapping(true);
    };

    return (
      <>
        <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '10px' }}>
          {[0, 250, 1000].map((ms) => (
            <button key={ms} onClick={() => setHoldMs(ms)} aria-pressed={ms === holdMs}>Hold {ms}ms</button>
          ))}
          <button onClick={toggleFlap} aria-pressed={flapping}>
            {flapping ? 'Stop the flapping feed' : 'Start the flapping feed'}
          </button>
          <button onClick={() => setHostRows((rows) => rows.map((h) => (h.id === 'web-1' ? { ...h, cpu: 99 } : h)))}>
            Spike web-1 to 99%
          </button>
          <button onClick={() => setHostRows(HOSTS)}>Calm everything</button>
        </div>
        <p>raised {tally.raised}, cleared {tally.cleared}</p>
        <div style={{ display: 'grid', gridTemplateColumns: '260px minmax(0,1fr) minmax(0,1fr)', gap: '16px' }}>
          <L.LatticeKPI
            ref={kpiRef}
            grid={hostsGrid}
            tiles={[
              { id: 'peak', label: 'Peak CPU', aggregation: 'max', field: 'cpu', thresholds: CPU },
              { id: 'mean', label: 'Mean CPU', aggregation: 'avg', field: 'cpu', thresholds: CPU },
            ]}
          />
          <L.LatticeGrid
            rowKey="id"
            columns={[
              { field: 'host', title: 'Host', layout: 120 },
              { field: 'cpu', type: 'number', title: 'CPU %', layout: 90 },
            ]}
            rows={hostRows}
            onGridReady={setHostsGrid}
            style={{ height: '260px' }}
          />
          <L.LatticeGrid
            rowKey="id"
            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 },
            ]}
            rows={[]}
            onGridReady={setWallGrid}
            style={{ height: '260px' }}
          />
        </div>
      </>
    );
  }

  createRoot(document.getElementById('app')).render(<App />);
</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.