demo D262
IoT Fleet Telemetry Dashboard: A Grid Per Device in React
One telemetry feed, a live grid per device class and a units-online chart, with no backend
createDataRouter · MockWebSocket · rng
This is a ready-made fleet telemetry screen: one telemetry feed drives a live grid per device class and a units-online chart, with no backend. It is a working layout for monitoring many devices from one stream, ready to point at real hardware.
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';
import { MockWebSocket } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/mock-socket.esm.min.js';
const { LatticeGrid, LatticeChart, useLatticeRouter, LatticeRouterProvider } =
createLatticeReact({ React, createGrid, createChart, createDataRouter });
// A feed is any generator that yields { kind: 'snapshot', rows } first, then
// { kind: 'delta', changes } forever; each record carries a kind and an id.
// The vendored mock-socket module ships no ready-made fleet feed, so this
// one is written here, to the same contract.
function* fleetFeed() {
const kinds = [
{ kind: 'truck', n: 4, extra: () => ({ speed: 40 + Math.round(Math.random() * 40), fuel: 30 + Math.round(Math.random() * 60) }) },
{ kind: 'drone', n: 3, extra: () => ({ altitude: 80 + Math.round(Math.random() * 300), battery: 20 + Math.round(Math.random() * 70) }) },
{ kind: 'sensor', n: 3, extra: () => ({ tempC: 10 + Math.round(Math.random() * 25), battery: 20 + Math.round(Math.random() * 70) }) },
];
const regions = ['EMEA', 'AMER', 'APAC'];
const rows = [];
for (const k of kinds) for (let i = 0; i < k.n; i++) {
rows.push({ id: k.kind + '-' + i, kind: k.kind, region: regions[i % regions.length], status: 'ok', ...k.extra() });
}
yield { kind: 'snapshot', rows };
while (true) {
const r = rows[Math.floor(Math.random() * rows.length)];
const k = kinds.find((x) => x.kind === r.kind);
yield { kind: 'delta', changes: [{ op: 'upsert', row: { ...r, ...k.extra() } }] };
}
}
const unitColumn = { field: 'id', title: 'Unit', layout: { width: 92 } };
const regionColumn = { field: 'region', title: 'Region', filter: { type: 'set' } };
const statusColumn = { field: 'status', title: 'Status' };
const truckColumns = [unitColumn, regionColumn, { field: 'speed', title: 'Speed', type: 'number' }, { field: 'fuel', title: 'Fuel %', type: 'number' }, statusColumn];
const droneColumns = [unitColumn, regionColumn, { field: 'altitude', title: 'Alt (m)', type: 'number' }, { field: 'battery', title: 'Battery %', type: 'number' }, statusColumn];
const sensorColumns = [unitColumn, regionColumn, { field: 'tempC', title: 'Temp C', type: 'number' }, { field: 'battery', title: 'Battery %', type: 'number' }, statusColumn];
function App() {
const router = useLatticeRouter({ key: 'kind', rowKey: 'id' });
const [metricsGrid, setMetricsGrid] = React.useState(null);
React.useEffect(() => {
if (!router) return undefined;
const metrics = createHeadlessGrid({ rowKey: 't', columns: [
{ field: 't', type: 'number' }, { field: 'online', type: 'number' },
] });
router.attach(metrics, 'metric', { rowKey: 't' });
setMetricsGrid(metrics);
const socket = new MockWebSocket({ feed: fleetFeed(), rate: 800, jitter: 200 });
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.kind === 'snapshot') router.load(message.rows);
else router.apply(message.changes);
};
return () => { socket.close(); metrics.destroy(); };
}, [router]);
return (
<LatticeRouterProvider router={router}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '14px' }}>
<LatticeGrid route="truck" rowKey="id" highlightOnChange={{ duration: 400 }} columns={truckColumns} rows={[]} style={{ height: '300px' }} />
<LatticeGrid route="drone" rowKey="id" highlightOnChange={{ duration: 400 }} columns={droneColumns} rows={[]} style={{ height: '300px' }} />
<LatticeGrid route="sensor" rowKey="id" highlightOnChange={{ duration: 400 }} columns={sensorColumns} rows={[]} style={{ height: '300px' }} />
</div>
{metricsGrid && <LatticeChart grid={metricsGrid} type="line" x="t" y="online" title="Units online per tick" style={{ height: '200px', marginTop: '14px' }} />}
</LatticeRouterProvider>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>