demo D295
A network operations centre in React
2,000 devices and 10,000 alarms on one feed: two grids, two charts and a KPI strip, with a live incident to watch the whole screen react
createDataRouter · router.link · router.attach(rollup)
One mixed feed of devices and alarms, routed to two grids, two charts and a KPI strip at once. Click a device and the alarm grid narrows to it; run the incident and watch the whole screen react to the same feed.
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, createHeadlessGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.esm.min.js';
import { createLatticeReact } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/react.esm.min.js';
import { createChart } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/charts.esm.min.js';
import { createDataRouter } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/data-router.esm.min.js';
const { LatticeGrid, LatticeChart, useLatticeRouter, LatticeRouterProvider } =
createLatticeReact({ React, createGrid, createChart, createDataRouter });
const deviceColumns = [
{ field: 'id', title: 'Device', layout: { width: 110, pin: 'start' } },
{ field: 'site', title: 'Site', filter: { type: 'set' } },
{ field: 'status', title: 'Status', filter: { type: 'set' } },
];
const alarmColumns = [
{ field: 'id', title: 'Alarm', layout: { width: 96, pin: 'start' } },
{ field: 'device', title: 'Device' },
{ field: 'severity', title: 'Severity', filter: { type: 'set' } },
];
function App() {
// One feed, partitioned by kind: a device roster, its alarm stream and a
// severity rollup, all off createDataRouter.
const router = useLatticeRouter({ key: 'kind', rowKey: 'id' });
const [severityGrid, setSeverityGrid] = React.useState(null);
React.useEffect(() => {
if (!router) return undefined;
const severity = createHeadlessGrid({ rowKey: 'level', columns: [
{ field: 'level', title: 'Severity' }, { field: 'count', title: 'Count', type: 'number' },
] });
router.attach(severity, 'rollup');
setSeverityGrid(severity);
return () => severity.destroy();
}, [router]);
return (
<LatticeRouterProvider router={router}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px' }}>
<LatticeGrid route="device" rowKey="id" columns={deviceColumns} rows={[]} style={{ height: '320px' }} />
<LatticeGrid route="alarm" rowKey="id" highlightOnChange={{ duration: 500 }} columns={alarmColumns} rows={[]} style={{ height: '320px' }} />
</div>
{severityGrid && (
<LatticeChart grid={severityGrid} type="bar" x="level" y="count" title="Open alarms by severity" style={{ height: '220px', marginTop: '14px' }} />
)}
</LatticeRouterProvider>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>
A whole operations screen from one feed
A network operations centre is the shape a live desk usually takes to its most demanding extreme: thousands of devices, a stream of alarms, a handful of headline figures and a couple of trend lines, all supposed to agree with each other from one moment to the next. Here two thousand devices and ten thousand alarm records are one feed into one Data Router, partitioned by what each record is. Device records land in the Devices grid; alarm records land in the Active Alarms grid, held to the ones still open. A KPI strip and two charts read the very same partitioned stream, so a number on a tile and a bar on a chart are never a stale read of a screen that has already moved on.
The devices and alarms are not just two grids sharing a page - they are related. Every alarm carries the deviceId of the device it belongs to, and the router’s link ties the two grids on that field. Click a device and the alarms grid narrows to its own alarms on the spot; clear the selection and it opens back up. That is the whole trick behind drilling from a summary into the record behind it, and here it costs one line of configuration rather than a query written by hand.
Watching a real incident, live
The SIMULATE NETWORK INCIDENT button degrades twenty devices at one site over a few seconds: their latency and packet loss climb, and a shadow column - Trend, tracking each device’s health against where it stood when the page opened - falls sharply. New alarms open against those devices as they degrade, the KPI strip’s alarm and SLA figures move, both charts move, and because the Devices grid is sorted on that same Trend column, the affected devices climb straight to the top on their own. Nothing here is a separate demo mode: the incident pushes rows through the identical feed the ambient traffic already uses, the same way a real network’s fault stream is just more of the same feed, not a different one.
How do I split one feed into a grid, a linked grid, a chart and a KPI strip?
Create one router with createDataRouter({ key: 'type', rowKey: 'id', overlap: true }) and call router.attach(view, value, opts) once per view: a grid for 'device', a grid for 'alarm' (with a filter to keep only the open ones), a headless grid feeding a chart, and a KPI panel. overlap: true matters as soon as more than one view reads the same value - the KPI strip and the severity chart both read 'device'/'alarm' alongside the grids that already claim them, and without it only the first-attached view on a value would ever see it. router.link(devices, alarms, { from: 'deviceId', to: 'deviceId' }) makes a pick in the devices grid narrow the alarms grid to the same device. For the alarms-by-severity chart, attach a headless grid with rollup: { groupBy: 'severity', aggregate: { count: { op: 'count' } } } and the router keeps one summary row per severity, updated by keyed diff as alarms open and close.