demo D262
Starter kit: fleet telemetry
One telemetry feed, a live grid per device class and a units-online chart, with no backend
createDataRouter · MockWebSocket · rng
Loading a live grid…
The configuration
'starter-fleet-telemetry': () => ({
rows: [],
config: {},
foot: [
'one telemetry feed, a live grid per device class',
'each class has its own columns, split from the one feed by kind',
'the mock socket runs it with no backend; one line points it at real telemetry',
],
mount: (el: HTMLElement, LG: any) => {
const strip = document.createElement('div');
strip.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px';
el.append(strip);
const trucksEl = panel(strip, 'Trucks');
const dronesEl = panel(strip, 'Drones');
const sensorsEl = panel(strip, 'Sensors');
const chartEl = chartHost(el);
const trucks = LG.createGrid(trucksEl, {
rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
columns: [
{ field: 'id', title: 'Unit', layout: { width: 92 } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'speed', title: 'Speed', type: 'number' },
{ field: 'fuel', title: 'Fuel %', type: 'number' },
{ field: 'status', title: 'Status' },
],
});
const drones = LG.createGrid(dronesEl, {
rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
columns: [
{ field: 'id', title: 'Unit', layout: { width: 92 } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'altitude', title: 'Alt (m)', type: 'number' },
{ field: 'battery', title: 'Battery %', type: 'number' },
{ field: 'status', title: 'Status' },
],
});
const sensors = LG.createGrid(sensorsEl, {
rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
columns: [
{ field: 'id', title: 'Unit', layout: { width: 92 } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'tempC', title: 'Temp C', type: 'number' },
{ field: 'battery', title: 'Battery %', type: 'number' },
{ field: 'status', title: 'Status' },
],
});
const metrics = LG.createHeadlessGrid({
rowKey: 't', columns: [{ field: 't', type: 'number' }, { field: 'online', type: 'number' }],
});
let chart: any, socket: any;
Promise.all([loadDataRouter(), loadCharts(), loadMockSocket()])
.then(([dr, ch, ms]: any[]) => {
const rng = ms.rng;
function* fleetFeed(seed: number) {
const rand = rng(seed);
const REGIONS = ['North', 'South', 'East', 'West'];
const pick = (list: string[]) => list[Math.floor(rand() * list.length)];
const round = (n: number, p: number) => { const f = Math.pow(10, p); return Math.round(n * f) / f; };
const trucksD: any[] = [], dronesD: any[] = [], sensorsD: any[] = [];
for (let i = 0; i < 14; i++) trucksD.push({ id: 'TRK-' + (100 + i), kind: 'truck', region: pick(REGIONS), speed: Math.round(rand() * 70), fuel: 20 + Math.round(rand() * 80), status: 'en route' });
for (let j = 0; j < 10; j++) dronesD.push({ id: 'DRN-' + (200 + j), kind: 'drone', region: pick(REGIONS), altitude: 40 + Math.round(rand() * 260), battery: 30 + Math.round(rand() * 70), status: 'in flight' });
for (let k = 0; k < 18; k++) sensorsD.push({ id: 'SNS-' + (300 + k), kind: 'sensor', region: pick(REGIONS), tempC: round(4 + rand() * 30, 1), battery: 30 + Math.round(rand() * 70), status: 'ok' });
const all = trucksD.concat(dronesD, sensorsD);
let t = 0;
const online = () => ({ id: 'M-' + t, kind: 'metric', t, online: all.filter((d) => d.status !== 'offline').length });
yield { kind: 'snapshot', rows: all.concat([online()]) };
for (;;) {
const changes: any[] = [];
const n = 2 + Math.floor(rand() * 4);
for (let m = 0; m < n; m++) {
const d = all[Math.floor(rand() * all.length)];
if (d.kind === 'truck') { d.speed = Math.round(rand() * 70); d.fuel = Math.max(0, d.fuel - Math.round(rand() * 3)); d.status = d.fuel < 10 ? 'low fuel' : 'en route'; }
else if (d.kind === 'drone') { d.altitude = 40 + Math.round(rand() * 260); d.battery = Math.max(0, d.battery - Math.round(rand() * 4)); d.status = d.battery < 15 ? 'returning' : 'in flight'; }
else { d.tempC = round(4 + rand() * 30, 1); d.battery = Math.max(0, d.battery - Math.round(rand() * 2)); d.status = d.tempC > 30 ? 'alert' : 'ok'; }
changes.push({ op: 'upsert', row: { ...d } });
}
t++;
changes.push({ op: 'upsert', row: online() });
yield { kind: 'delta', changes };
}
}
const router = dr.createDataRouter({ key: 'kind', rowKey: 'id' });
router.attach(trucks, 'truck');
router.attach(drones, 'drone');
router.attach(sensors, 'sensor');
router.attach(metrics, 'metric', { rowKey: 't' });
chart = ch.createChart({ grid: metrics, container: chartEl, type: 'line', x: 't', y: 'online', title: 'Units online per tick' });
socket = new ms.MockWebSocket({ feed: fleetFeed(11), rate: 800, jitter: 200 });
pipe(socket, router);
})
.catch((err) => console.error('[starter-fleet-telemetry]', err));
return () => {
socket?.close?.();
chart?.destroy?.();
trucks?.destroy?.(); drones?.destroy?.(); sensors?.destroy?.(); metrics?.destroy?.();
};
},
})