Keal
Back to app

Events

Most of what Keal shows changes without anyone asking it to: a VM finishes pausing, a compute node reports in, a backup completes half an hour after the request that started it returned. Those arrive in the browser as Server-Sent Events, pushed from the Perl backend rather than polled for.

The path an event takes

Anything in the backend that knows something changed calls updateUI, which hands the event to postEvent in cgi/Stabile.pm. That posts it to Keal, which fans it out to whichever browsers are entitled to see it.

Why it is not a poll

The backend knows more than any before-and-after comparison could tell. When a backup finishes, steamExec reads the backup tool's output and says what happened — Backed up 204.8 MB (214748364 bytes), or why it failed. A poll would only ever see that the status changed.

It is also quick: posting an event to Keal costs a couple of milliseconds, and it reaches the browser at once instead of at the next reload.

Audiences

An event reaches only the people it concerns:

Event Who receives it
Owned by a user That user
tab: nodes Administrators
Owned by irigo or common Administrators

Admin status is resolved once per connection, not per event — these connections are long-lived, so the cost is paid once per browser tab.

Topics

Each event from the backend is published under stabile.<tab> — stabile.images, stabile.servers — and the monitors' under keal.monitors. A tab module subscribes to just its own resource:

onServerEvent('stabile.images', function (d) {
    if (d.message) Keal.toast(d.message, 'info');
    if (d.uuid && d.status) applyStatusUpdate(d.uuid, d.status);
});

One EventSource is shared by the whole app and multiplexed by topic (events.js). A browser allows only a handful of concurrent connections per origin, so a stream per feature would exhaust that budget.

Best effort, deliberately

Events are not persisted, not replayed after a reconnect, and not acknowledged. A browser that was disconnected for a few seconds has simply missed what happened.

Push is latency reduction, not the source of truth

Every consumer keeps its own periodic refresh — the tabs reload every minute or two regardless. That backstop is what makes a dropped event a delay rather than a permanently wrong screen.

Authentication

The publishers are backend processes, some running as root, with no ticket of their own. They authenticate with the SHA-512 of the shared TKTAuthSecret — the same hash the engine uses to prove itself to the registry.

A loopback check would be no substitute: Apache reverse-proxies this vhost, so every request arrives from 127.0.0.1 regardless of where it came from.

Adding a producer

From Python, publish onto the hub directly:

from api import events

await events.publish(
    "stabile.images",
    {"uuid": uuid, "status": "unused", "message": "Done"},
    events.Audience.user_scoped(user),
)

From Perl, call updateUI as the resource scripts already do — postEvent happens underneath it, and nothing else is required.