Skip to content

Real-time Updates

The Mantis dashboard uses Server-Sent Events (SSE) for live updates without page refresh.

Data updates are sent as unnamed SSE messages (default message event); the event type is carried inside the JSON payload, not in an SSE event: field. Use onmessage (not addEventListener('created', …) for data events) and switch on payload.type and payload.resource_type. The only named SSE events are the connected handshake and lagged (buffer-overflow) control events.

: data updates — unnamed (default "message") events, type is inside the payload
data: {"type":"updated","resource_type":"deployments","resource_id":"abc123","data":{...},...}
data: {"type":"updated","resource_type":"targets","resource_id":"def456","data":{...},...}
: control events are named
event: connected
data: {}

The dashboard shows connection status:

StateBadgeDescription
Connected🟢 LiveReceiving real-time updates
Connecting / Reconnecting🟡 Connecting…Establishing or re-establishing connection
Disconnected / Failed(none)No live updates

Both the connecting and reconnecting states show the same Connecting… badge. Once the client exhausts all retry attempts the state becomes failed, which also shows no badge — use the manual refresh button to get the latest data.

┌─────────────────────────────────────────────────────────────┐
│ Dashboard Period: [Last 7 Days ▼] 🟢 Live [🔄] │
├─────────────────────────────────────────────────────────────┤
│ │
│ Statistics update automatically when changes occur │
│ │
└─────────────────────────────────────────────────────────────┘

All SSE data events share a common envelope. The type field is always one of the generic CRUD operations; the resource_type field identifies the entity kind.

typeMeaning
createdA new entity was created
updatedAn existing entity was changed
deletedAn entity was removed

The only named (non-message) SSE events are the connected handshake and lagged (buffer-overflow) control events — listen for those with addEventListener('connected', …) / addEventListener('lagged', …).

{
"type": "updated",
"resource_type": "deployments",
"resource_id": "abc123",
"data": { ... },
"tenant_id": "tenant-uuid-or-null",
"changed_fields": ["status", "progress"],
"timestamp": "2024-01-21T14:30:00Z"
}

resource_type values include statistics, tenant_statistics, deployments, targets, environments, solutions, actions, sequences, registrations, freezes, tags, users, roles, tenants, certificates, storage_sources, identity_providers, thorax_instances, failure_patterns, and failure_pattern_stats.

Use onmessage and switch on event.data.type plus event.data.resource_type to identify the change.

The dashboard automatically subscribes on mount:

┌─────────────────────────────────────────────────────────────┐
│ Page Load Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Dashboard component mounts │
│ 2. Load initial statistics via REST API │
│ 3. Open SSE connection for live updates │
│ 4. Display connection status │
│ 5. Receive and apply incremental updates │
│ │
└─────────────────────────────────────────────────────────────┘

Connections are properly closed:

TriggerAction
Navigate awayUnsubscribe from SSE
Component unmountClose connection
LogoutTerminate all subscriptions
Tab hiddenOptionally pause (future)

The SSE client automatically reconnects:

The delay before each retry is min(initialRetryDelay * 2^attempt, maxRetryDelay) + jitter, where initialRetryDelay is 1 second, maxRetryDelay caps at 30 seconds, and jitter is a random value in [0, 1000 ms] added to prevent thundering-herd reconnects. The client makes up to 10 reconnection attempts by default.

The table below shows the base delay before jitter; actual delays will be up to 1 second longer.

AttemptBase DelayActual Range
11 second1–2s
22 seconds2–3s
34 seconds4–5s
48 seconds8–9s
516 seconds16–17s
6+30 seconds (max)30–31s

If events are missed during disconnection:

┌─────────────────────────────────────────────────────────────┐
│ SSE Lag Handler │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Detect missed events (sequence gap) │
│ 2. Log warning: "Missed X events" │
│ 3. Trigger full data refresh │
│ 4. Resume normal event processing │
│ │
└─────────────────────────────────────────────────────────────┘

Administrators receive updates for all tenants:

FilterEvents Received
tenant_id: nullGlobal aggregates
No filterAll tenant events

Regular users receive updates for their tenant only:

FilterEvents Received
tenant_id: "abc123"Specific tenant
Own tenant onlyScoped updates

Statistics cards update immediately:

Before Event:
┌───────────┐
│ Succeeded │
│ 142 │
└───────────┘
Event Received: type=updated, resource_type=statistics
After Event:
┌───────────┐
│ Succeeded │
│ 143 │ ← Incremented
└───────────┘

The trend chart updates on significant changes:

Update TriggerAction
New dayAdd new data point
Deployment completedUpdate today’s count
Period changeReload full trend

New activities prepend to the list:

┌───────────────────────────────────────────────────────────┐
│ Recent Activity │
├───────────────────────────────────────────────────────────┤
│ │
│ ● Just now Deployment succeeded ← NEW │
│ web-app v1.2.4 → web-prod-02 │
│ │
│ ● 2 min ago Deployment succeeded │
│ web-app v1.2.3 → web-prod-01 │
│ │
│ ● 15 min ago Target registered │
│ api-staging-02 came online │
│ │
└───────────────────────────────────────────────────────────┘

Real-time target status changes:

The thresholds are server-side defaults: 90 seconds for Online→Stale and 5 minutes (300 seconds) for Stale→Offline. They can be overridden via the session.heartbeat_timeout_secs and session.stale_timeout_secs keys in the [session] block of the Thorax configuration file (file-only; no THORAX__SESSION__* env vars are recognized).

Target health counts update live:

Event: type=updated, resource_type=targets, data.status="stale"
Before:
┌──────────┬──────────┬──────────┬──────────┐
│ Total │ Online │ Stale │ Offline │
│ 24 │ 21 │ 1 │ 2 │
└──────────┴──────────┴──────────┴──────────┘
After:
┌──────────┬──────────┬──────────┬──────────┐
│ Total │ Online │ Stale │ Offline │
│ 24 │ 20 │ 2 │ 2 │
└──────────┴──────────┴──────────┴──────────┘

The SSE stream has no server-side event throttling and no client-side event-rate limiting or event-history cap. The server holds up to 1024 events per resource type in its broadcast buffer (CHANNEL_CAPACITY); a client that falls behind is dropped and must reconnect. The only client-side debounce is a 200ms coalescing of network topology rebuilds (specific to the topology view), not the general event stream. Connections are released on navigation.

BrowserSSE SupportNotes
Chrome✅ FullNative EventSource
Firefox✅ FullNative EventSource
Safari✅ FullNative EventSource
Edge✅ FullNative EventSource

If SSE is unavailable:

┌─────────────────────────────────────────────────────────────┐
│ Fallback: Manual Refresh │
├─────────────────────────────────────────────────────────────┤
│ │
│ • SSE connection failed │
│ • Live badge not shown │
│ • Use refresh button for updates │
│ • Consider polling implementation (future) │
│ │
└─────────────────────────────────────────────────────────────┘

Monitor SSE in Network tab:

  1. Open DevTools (F12)
  2. Go to Network tab
  3. Filter by “EventSource” or “sse”
  4. Click connection to see events
Name Status Type Size
/api/v1/sse/statistics 200 eventsource streaming
EventStream:
- message 14:30:00 {"type":"updated","resource_type":"statistics",...}
- message 14:30:15 {"type":"created","resource_type":"deployments",...}
- message 14:30:20 {"type":"updated","resource_type":"deployments",...}

The SSE client logs connection events:

// Connection established
SSE: Connected to /api/v1/sse/statistics
// Event received
SSE: Received updated/statistics event
// Connection lost
SSE: Connection lost, reconnecting in 2s...
// Lag detected
SSE: Lagged, missed 5 events. Refreshing data...
IssueSymptomSolution
Connection refusedNo live badgeCheck API availability
Events not updatingStale dataVerify subscription
Frequent reconnectsFlashing badgeCheck network stability
Memory growthBrowser slowdownRefresh page

SSE connections use a short-lived, single-use token passed in the ?sse_token= query parameter (not the regular JWT Authorization header, which EventSource cannot set). Obtain the token via POST /api/v1/auth/sse-token:

GET /api/v1/sse/{resource_type}?sse_token=eyJhbG...

Valid resource_type values include statistics, deployments, targets, environments, solutions, and others. See the API Authentication guide for the full token flow.

Events are filtered by tenant:

RoleEvents Visible
AdminAll tenants
UserOwn tenant only

The current implementation enforces no per-user connection cap and no idle timeout. The server sends an SSE keepalive comment every 3 seconds (SSE_KEEPALIVE_SECS) to keep connections (and intermediate proxies) from timing out.

Always check the live indicator:

IndicatorMeaning
🟢 LiveUpdates are automatic
🟡 ConnectingWait for connection
(none)Use manual refresh

If browser was inactive:

  1. Check connection status
  2. Click refresh button
  3. Verify data is current

Each tab maintains its own connection:

  • Close unused tabs
  • Consider single-tab usage for dashboards
  • Browser may throttle background tabs

For critical decisions:

  1. Note the live indicator
  2. Click refresh for certainty
  3. Check timestamp on data

Cause: SSE connection failed

Solution:

  1. Check browser console for errors
  2. Verify network connectivity
  3. Check API server health
  4. Clear browser cache

Cause: Missed events during reconnection

Solution:

  1. Click manual refresh
  2. Check connection status
  3. Verify no proxy interference

Cause: Too many events or memory leak

Solution:

  1. Refresh the page
  2. Close other tabs
  3. Check event frequency