@nolag/dash

Live dashboards with real-time metrics, time-series aggregation, and widgets.

Overview

@nolag/dash powers live operational dashboards with streaming metrics and interactive widgets. Agents or servers publish metric data points and widget state snapshots to panels; viewers connected to the same panel receive updates instantly and can query rolling aggregations (average, min, max, sum, count, last) over any time window without hitting a separate time-series database. Your app owns one core NoLag client and injects it into NoLagDash; the wrapper attaches its behaviour to that connection.

Key Features

  • Streaming metric ingestion with per-stream data point buffering
  • Client-side rolling aggregation over configurable time windows
  • Widget state publishing for gauges, charts, counters, tables, text, and custom types
  • Tag-based metadata on metrics for multi-dimensional filtering
  • Automatic reconnection with panel subscriptions restored

How It Works

NoLagDash attaches to an injected @nolag/js-sdk client. Calling joinPanel() creates a DashboardPanel that subscribes to two topics: metrics for time-series data points and widgets for widget state snapshots. A MetricStore inside the panel accumulates data points per stream ID and provides rolling-window aggregation, while a WidgetManager keeps the latest state for each widget keyed by widget ID. The app owns the socket lifecycle; the wrapper never opens or closes it.

TopicPurpose
metricsTime-series data points: value, timestamp, unit, tags
widgetsWidget state snapshots: type, data payload, label, timestamp

A viewer's buffer starts empty and fills from the data points published after it joined. A fresh subscribe or an ordinary reconnect never replays history; see Replay for the one case where the broker replays messages. If new viewers need a starting state, have your publisher re-send its current widget snapshots on a timer.

Before you start. Create an app from the nolag-dash-sdk blueprint. That seeds the panel this SDK expects (overview) and the online lobby, and returns an app slug with a random suffix. That slug is the appName you pass to the wrapper. You can also do this in the portal: Apps, New App, pick the blueprint.

import { NoLagApi } from "@nolag/js-sdk";

const api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...
const app = await api.apps.create({ name: "My App", blueprintId: "nolag-dash-sdk" });
console.log(app.slug); // e.g. "my-app-a3f9": this is your appName

const actor = await api.actors.create({ name: "web-client", actorType: "user" });
console.log(actor.accessToken); // shown once; keep it

Installation

npm install @nolag/dash @nolag/js-sdk

Shared connection. One core NoLag client can back several wrapper SDKs at once, for example a dashboard, chat, and notify on a single socket, as long as each wrapper uses a distinct appName. Each wrapper attaches its handlers on construction and releases them with detach(), and never touches the socket itself. Your app owns connect() and disconnect().

Quick Start

import { NoLag } from '@nolag/js-sdk'
import { NoLagDash } from '@nolag/dash'

// The app owns one core client. In a browser, pass a token provider so the
// SDK can mint fresh short-lived client tokens from your backend.
const client = NoLag(async () => (await (await fetch('/api/nolag-token')).json()).token)

// Inject the client into the dash wrapper
const dash = new NoLagDash({
  client,
  appName: APP_SLUG, // the slug returned when you created the app
  username: 'Alice',
})

await client.connect()   // the app owns the connection
await dash.ready()       // wrapper setup complete

// Join the seeded panel
const panel = dash.joinPanel('overview')

// Publish a metric (typically called from a server or agent). The point is
// stamped with Date.now() and added to the publisher's own buffer.
panel.publishMetric('cpu.usage', 74.2, {
  unit: 'percent',
  tags: { host: 'web-01', region: 'us-east-1' },
})

// Listen for incoming metrics from other publishers
panel.on('metric', ({ streamId, value, timestamp }) => {
  console.log(`[${streamId}] ${value} @ ${new Date(timestamp).toISOString()}`)
})

// Get all buffered data points for a metric stream
const points = panel.getMetrics('cpu.usage')
console.log('Data points:', points.length)

// Get a rolling aggregation over the last 5 minutes
const agg = panel.getAggregation('cpu.usage', 5 * 60_000)
console.log('Avg CPU:', agg.avg.toFixed(1) + '%')
console.log('Max CPU:', agg.max.toFixed(1) + '%')

// Publish a widget state (e.g. a status counter)
panel.publishWidget('active-sessions', 'counter', { value: 1_248, trend: 'up' }, 'Active Sessions')

// Listen for widget updates
panel.on('widgetUpdate', ({ widgetId, type, data }) => {
  console.log(`Widget ${widgetId} (${type}):`, data)
})

// Read current widget state
const widget = panel.getWidget('active-sessions')

// Teardown: the wrapper releases its handlers; the app closes the socket.
dash.detach()
client.disconnect()

API Reference

NoLagDash

The main class. Attaches to the injected core client and manages the dashboard panel lifecycle.

Constructor Options

OptionTypeDescription
clientNoLagSocketRequired. The injected core NoLag client the app owns and connects.
usernamestringOptional display name for this viewer.
metadataRecord<string, unknown>Optional custom viewer data attached to presence.
appNamestringThe app slug returned when you created the app (default 'dash', which only works if an app with exactly that slug exists).
panelsstring[]Panels to subscribe to once the wrapper is ready.
maxMetricPointsnumberMax metric points kept in memory per stream (default 1000).
aggregationWindownumberAccepted and stored (default 60000) but not read yet: getAggregation() uses 60000 ms whenever windowMs is omitted.
debugbooleanEnable wrapper debug logging (default false).
MethodReturnsDescription
ready()Promise<void>Resolves once wrapper setup completed
detach()voidRelease this wrapper's handlers and topics; terminal, never closes the socket
joinPanel(name, opts?)DashboardPanelJoin a dashboard panel; opts.filters limits metrics and widgets to matching values; throws before ready()
leavePanel(name)voidLeave a panel and unsubscribe from its topics
getPanels()DashboardPanel[]All joined panels
getOnlineViewers()DashboardViewer[]All viewers currently online in the lobby

Events: NoLagDash

EventPayloadDescription
connectednoneWrapper setup completed on a fresh connection
disconnectedreason: stringConnection closed
reconnectingnoneThe client is attempting to reconnect
reconnectednoneReconnection successful; panels are restored automatically
errorerror: ErrorUnrecoverable error occurred
viewerOnlineviewer: DashboardViewerA viewer appeared in the lobby
viewerOfflineviewer: DashboardViewerA viewer left the lobby

DashboardPanel

Returned by joinPanel(). Handles metric ingestion, aggregation, widget state, and per-panel viewer presence.

MethodReturnsDescription
publishMetric(streamId, value, opts?)MetricPointPublish a data point; opts accepts unit, tags, filter, filters. The timestamp is always Date.now()
publishWidget(widgetId, type, data, label?, opts?)WidgetUpdatePublish a widget snapshot; replaces the previous state for that widget ID. type is 'gauge' | 'chart' | 'counter' | 'table' | 'text' | 'custom'
getMetrics(streamId?)MetricPoint[]Buffered data points for one stream, or every stream when omitted
getAggregation(streamId, windowMs?)Aggregation{ streamId, min, max, avg, sum, count, last, windowMs } over the last windowMs ms (60000 when omitted)
getWidget(widgetId)WidgetUpdate | undefinedLatest state snapshot for a single widget
getWidgets()WidgetUpdate[]Latest state snapshots for all widgets in this panel
getViewers()DashboardViewer[]Remote viewers present in this panel
setFilters(values, opts?)voidReplace the panel's subscription filters; see Filters

Events: DashboardPanel

EventPayloadDescription
metricMetricPointA data point arrived: { id, streamId, value, unit?, tags?, timestamp, isReplay }
widgetUpdateWidgetUpdateA widget snapshot arrived: { id, widgetId, type, data, label?, timestamp, isReplay }
viewerJoinedviewer: DashboardViewerA viewer joined this panel
viewerLeftviewer: DashboardViewerA viewer left this panel