Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tutorial 34: Live Analytics Dashboard (rullst::live & WebSockets) 📈

Build a per-connection analytics view with rullst::live. Feed it values from your own authoritative metrics source; the example below uses explicit state only to demonstrate the component lifecycle.


🛠️ Step 1: Create Analytics LiveComponent

cargo rullst make:live AnalyticsDashboard writes src/live/analytics_dashboard.rs and declares it in src/live/mod.rs; declare mod live; in the crate root yourself. Replace the generated counter with:

#![allow(unused)]
fn main() {
use rullst::async_trait;
use rullst::live::LiveComponent;
use serde_json::Value;

#[derive(Default)]
pub struct AnalyticsDashboard {
    pub revenue_mrr: f64,
    pub active_users: usize,
}

#[async_trait]
impl LiveComponent for AnalyticsDashboard {
    async fn mount(&mut self) {
        // Replace these initial values with an application-owned metrics query.
        self.revenue_mrr = 0.0;
        self.active_users = 0;
    }

    async fn handle_event(&mut self, payload: Value) {
        if let Some(event) = payload.get("event").and_then(|v| v.as_str()) {
            if event == "refresh" {
                self.revenue_mrr += 100.00;
                self.active_users += 1;
            }
        }
    }

    fn render(&self) -> String {
        format!(
            r#"<div id="analytics-dashboard" class="p-8 bg-slate-900 text-white rounded-2xl shadow-2xl border border-slate-800">
    <h2 class="text-2xl font-bold mb-6">Live Analytics Stream</h2>
    <div class="grid grid-cols-2 gap-6 mb-6">
        <div class="p-4 bg-slate-800 rounded-xl">
            <p class="text-slate-400 text-sm">MRR</p>
            <p class="text-3xl font-mono text-emerald-400">${:.2}</p>
        </div>
        <div class="p-4 bg-slate-800 rounded-xl">
            <p class="text-slate-400 text-sm">Active Users</p>
            <p class="text-3xl font-mono text-cyan-400">{}</p>
        </div>
    </div>
    <button ws-send name="event" value="refresh" class="px-6 py-3 bg-indigo-600 hover:bg-indigo-500 font-semibold rounded-xl">
        ⚡ Refresh Live Stream
    </button>
</div>"#,
            self.revenue_mrr, self.active_users
        )
    }
}
}

💻 Step 2: Render in View Page

use rullst::html::Markup;
use rullst::live::Live;
use crate::live::analytics_dashboard::AnalyticsDashboard;

pub async fn analytics_page() -> Markup {
    Live::mount::<AnalyticsDashboard>("/ws/analytics").await
}

Live::mount pre-renders the component inside a wrapper that opens the WebSocket; return it as the page or interpolate it into an html! layout. Register the matching upgrade route on the application’s rullst::Router (generated projects reach Axum only through rullst::web::axum):

let app = router()
    .ws("/ws/analytics", rullst::live::live_ws_handler::<AnalyticsDashboard>);

The page must also load HTMX and a pinned HTMX WebSocket extension; Rullst does not inject that browser dependency.


💡 Key Takeaways

  • Rullst owns the server-side component lifecycle; a browser transport is still required.
  • The current implementation re-renders an HTML fragment after each valid JSON event. It does not provide distributed state, replay, authorization or a metrics source automatically.
  • Incoming events are limited to 64 KiB per WebSocket message; larger messages close the session.