Use a Laravel Reverb-compatible client to subscribe to resource events and render CPU, memory, disk, and status data in your application.
There are two contracts. Select one based on the resource type.
Use the resource name as the channel identity.
nodegraf.snapshotprivate-stack-{stack_name}stack_nameUse the service ID as the channel identity.
nodegraf.server_snapshotprivate-instant-app-server.{service_id}service_idKeep the signing secret on the server. The browser receives only public connection settings and a proof for the selected channel.
Load the resource identity and its allocation limits from your application.
Sign {service_id}|{client_id}|{channel_name} with your server-side Reverb secret.
Your auth endpoint checks the session and returns the standard Pusher channel signature.
const pusher = new Pusher(config.appKey, {
cluster: 'mt1',
wsHost: config.host,
wsPort: config.port,
wssPort: config.port,
wsPath: config.path,
forceTLS: config.scheme === 'https',
enabledTransports: ['ws', 'wss'],
disableStats: true,
channelAuthorization: {
endpoint: config.authEndpoint,
transport: 'ajax',
params: {
csrfToken: config.csrfToken,
channelProof: config.channelProof
}
}
});
Subscribe to one resource channel and accept only events carrying the same resource name.
CPU usage uses the resource allocation supplied by your application. Memory and disk values are bytes.
stack_namecpu_cores_usedmemory_used_bytes, memory_limit_bytesdisk_used_bytessampled_at{
"node_name": "node-01",
"sampled_at": "2026-08-24T05:00:00Z",
"stack_name": "service-18106",
"cpu_cores_used": 0.3,
"memory_used_bytes": 415236096,
"memory_limit_bytes": 2147483648,
"disk_used_bytes": 125829120,
"disk_collected_at": "2026-08-24T04:59:30Z"
}
const channelName = `private-stack-${stackName}`;
const channel = pusher.subscribe(channelName);
channel.bind('nodegraf.snapshot', (payload) => {
if (payload.stack_name !== stackName) return;
renderUsage({
cpuPercent: round(payload.cpu_cores_used / cpuLimit * 100),
memoryUsedMb: round(payload.memory_used_bytes / 1048576),
diskUsedMb: payload.disk_used_bytes == null
? null
: round(payload.disk_used_bytes / 1048576),
sampledAt: payload.sampled_at
});
});
Subscribe to one server channel and accept only events carrying the same service ID.
CPU allocation is included in the event. Memory, disk, and status are reported for the selected server.
service_idvmid, vm_name, statuscpu_cores_used, cpu_cores_allocatedmemory_used_bytes, memory_limit_bytes{
"node_name": "pve-01",
"sampled_at": "2026-08-24T05:00:00Z",
"service_id": 1001,
"vmid": 1201,
"vm_name": "server-1001",
"status": "running",
"cpu_cores_used": 0.25,
"cpu_cores_allocated": 2,
"memory_used_bytes": 536870912,
"memory_limit_bytes": 4294967296,
"disk_used_bytes": 21474836480,
"disk_limit_bytes": 53687091200
}
const channelName = `private-instant-app-server.${serviceId}`;
const channel = pusher.subscribe(channelName);
channel.bind('nodegraf.server_snapshot', (payload) => {
if (Number(payload.service_id) !== Number(serviceId)) return;
renderUsage({
cpuPercent: round(payload.cpu_cores_used / payload.cpu_cores_allocated * 100),
memoryUsedMb: round(payload.memory_used_bytes / 1048576),
diskUsedMb: payload.disk_used_bytes == null
? null
: round(payload.disk_used_bytes / 1048576),
status: payload.status,
sampledAt: payload.sampled_at
});
});
Live metrics are best-effort. Keep allocation data visible when a live sample is unavailable.
| Condition | Client behavior |
|---|---|
| Wrong resource identity | Ignore the event. Never render it in the current view. |
| Missing optional metric | Render Unavailable; do not convert it to zero. |
| No event for 30 seconds | Mark live usage unavailable and keep the last known allocation. |
| Authorization or connection failure | Show a non-blocking error and stop retrying in a tight loop. |
| Resource changes | Unsubscribe from the old channel before subscribing to the new one. |
A Portainer consumer handles nodegraf.snapshot. A Proxmox consumer handles nodegraf.server_snapshot. Do not share their channel or identity validation.