Skip to content

Live Dashboard

This recipe demonstrates building a dashboard with real-time data updates using Datastar's interval observer to periodically fetch fresh data from the server.

What We're Building

A system monitoring dashboard with:

  • Auto-refresh every 5 seconds
  • Manual refresh button
  • Pause/resume functionality
  • Multiple metric cards
  • Last updated timestamp

Complete Implementation

Blade Template

Create resources/views/dashboard/index.blade.php:

blade
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Live Dashboard</title>
    @hyper
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50">
    <div class="max-w-7xl mx-auto px-4 py-8">
        <div @signals([
            'cpuUsage' => $metrics['cpuUsage'],
            'memoryUsage' => $metrics['memoryUsage'],
            'activeUsers' => $metrics['activeUsers'],
            'requestsPerMinute' => $metrics['requestsPerMinute'],
            'lastUpdated' => now()->format('H:i:s'),
            '_paused' => false
        ])>
            <!-- Header -->
            <div class="flex justify-between items-center mb-8">
                <h1 class="text-3xl font-bold">System Dashboard</h1>

                <div class="flex gap-3">
                    <button
                        data-on:click="$_paused = !$_paused"
                        data-class:bg-blue-600="!$_paused"
                        data-class:bg-gray-600="$_paused"
                        class="px-4 py-2 text-white rounded-md hover:opacity-90 transition-colors">
                        <span data-show="!$_paused">⏸ Pause</span>
                        <span data-show="$_paused">▶ Resume</span>
                    </button>

                    <button
                        data-on:click="@get('/dashboard/refresh?manual=1')"
                        class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors">
                        🔄 Refresh Now
                    </button>
                </div>
            </div>

            <!-- Status Indicator -->
            <div class="mb-6 flex items-center gap-4">
                <div class="flex items-center gap-2">
                    <div 
                        data-class:bg-green-500="!$_paused"
                        data-class:bg-gray-400="$_paused"
                        class="w-3 h-3 rounded-full animate-pulse">
                    </div>
                    <p class="text-sm text-gray-600">
                        <span data-show="!$_paused" class="text-green-600 font-medium">Auto-refresh active (every 5s)</span>
                        <span data-show="$_paused" class="text-gray-500 font-medium">Auto-refresh paused</span>
                    </p>
                </div>
                <p class="text-sm text-gray-500">
                    Last updated: <span data-text="$lastUpdated" class="font-mono"></span>
                </p>
            </div>

            <!-- Auto-refresh interval (only when not paused) -->
            <div>
                <div data-on-interval__duration.5s="if (!$_paused) { @get('/dashboard/refresh') }"></div>
            </div>

            <!-- Metrics Grid -->
            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
                <!-- CPU Usage Card -->
                <div class="bg-white rounded-lg shadow p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="text-gray-500 text-sm font-medium">CPU Usage</h3>
                        <svg class="w-8 h-8 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
                            <path d="M13 7H7v6h6V7z"></path>
                            <path fill-rule="evenodd" d="M7 2a1 1 0 012 0v1h2V2a1 1 0 112 0v1h2a2 2 0 012 2v2h1a1 1 0 110 2h-1v2h1a1 1 0 110 2h-1v2a2 2 0 01-2 2h-2v1a1 1 0 11-2 0v-1H9v1a1 1 0 11-2 0v-1H5a2 2 0 01-2-2v-2H2a1 1 0 110-2h1V9H2a1 1 0 010-2h1V5a2 2 0 012-2h2V2z"></path>
                        </svg>
                    </div>
                    <p class="text-3xl font-bold text-gray-900 mb-2" data-text="$cpuUsage + '%'"></p>
                    <div class="w-full bg-gray-200 rounded-full h-2">
                        <div
                            data-attr:style="'width: ' + $cpuUsage + '%'"
                            data-class:bg-red-500="$cpuUsage > 80"
                            data-class:bg-yellow-500="$cpuUsage > 50 && $cpuUsage <= 80"
                            data-class:bg-green-500="$cpuUsage <= 50"
                            class="h-2 rounded-full transition-all duration-500">
                        </div>
                    </div>
                </div>

                <!-- Memory Usage Card -->
                <div class="bg-white rounded-lg shadow p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="text-gray-500 text-sm font-medium">Memory Usage</h3>
                        <svg class="w-8 h-8 text-purple-500" fill="currentColor" viewBox="0 0 20 20">
                            <path d="M3 12v3c0 1.657 3.134 3 7 3s7-1.343 7-3v-3c0 1.657-3.134 3-7 3s-7-1.343-7-3z"></path>
                            <path d="M3 7v3c0 1.657 3.134 3 7 3s7-1.343 7-3V7c0 1.657-3.134 3-7 3S3 8.657 3 7z"></path>
                            <path d="M17 5c0 1.657-3.134 3-7 3S3 6.657 3 5s3.134-3 7-3 7 1.343 7 3z"></path>
                        </svg>
                    </div>
                    <p class="text-3xl font-bold text-gray-900 mb-2" data-text="$memoryUsage + '%'"></p>
                    <div class="w-full bg-gray-200 rounded-full h-2">
                        <div
                            data-attr:style="'width: ' + $memoryUsage + '%'"
                            data-class:bg-red-500="$memoryUsage > 80"
                            data-class:bg-yellow-500="$memoryUsage > 50 && $memoryUsage <= 80"
                            data-class:bg-green-500="$memoryUsage <= 50"
                            class="h-2 rounded-full transition-all duration-500">
                        </div>
                    </div>
                </div>

                <!-- Active Users Card -->
                <div class="bg-white rounded-lg shadow p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="text-gray-500 text-sm font-medium">Active Users</h3>
                        <svg class="w-8 h-8 text-green-500" fill="currentColor" viewBox="0 0 20 20">
                            <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"></path>
                        </svg>
                    </div>
                    <p class="text-3xl font-bold text-gray-900" data-text="$activeUsers"></p>
                    <p class="text-sm text-gray-500">Currently online</p>
                </div>

                <!-- Requests Per Minute Card -->
                <div class="bg-white rounded-lg shadow p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="text-gray-500 text-sm font-medium">Requests/Min</h3>
                        <svg class="w-8 h-8 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
                            <path fill-rule="evenodd" d="M12 7a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0V8.414l-4.293 4.293a1 1 0 01-1.414 0L8 10.414l-4.293 4.293a1 1 0 01-1.414-1.414l5-5a1 1 0 011.414 0L11 10.586 14.586 7H12z"></path>
                        </svg>
                    </div>
                    <p class="text-3xl font-bold text-gray-900" data-text="$requestsPerMinute"></p>
                    <p class="text-sm text-gray-500">Average throughput</p>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

Controller

Create app/Http/Controllers/DashboardController.php:

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class DashboardController extends Controller
{
    public function index()
    {
        $metrics = $this->getMetrics();

        return view('dashboard.index', compact('metrics'));
    }

    public function refresh(Request $request)
    {

        $metrics = $this->getMetrics();

        return hyper()->signals([
            'cpuUsage' => $metrics['cpuUsage'],
            'memoryUsage' => $metrics['memoryUsage'],
            'activeUsers' => $metrics['activeUsers'],
            'requestsPerMinute' => $metrics['requestsPerMinute'],
            'lastUpdated' => now()->format('H:i:s')
        ]);
    }

    protected function getMetrics(): array
    {
        return [
            'cpuUsage' => Cache::remember('cpu_usage', 5, fn() => rand(20, 95)),
            'memoryUsage' => Cache::remember('memory_usage', 5, fn() => rand(30, 85)),
            'activeUsers' => Cache::remember('active_users', 5, fn() => rand(10, 150)),
            'requestsPerMinute' => Cache::remember('requests_per_minute', 5, fn() => rand(50, 500))
        ];
    }
}

How It Works

Automatic Refresh

Datastar's data-on-interval triggers periodic updates:

blade
<div>
    <div data-on-interval__duration.5s="if (!$_paused) { @get('/dashboard/refresh') }"></div>
</div>

Breakdown:

  • data-on-interval (Datastar) - Repeats action at intervals
  • __5s (Datastar) - Every 5 seconds
  • @get(...) (Datastar) - Fetches updated metrics
  • if (!$_paused) - Only active when not paused

Pause/Resume

A local signal _paused controls auto-refresh:

blade
<button data-on:click="$_paused = !$_paused">
    <span data-text="$_paused ? 'Resume' : 'Pause'"></span>
</button>

When _paused is true, auto-refreshing is stopped .

Dynamic Progress Bars

Progress bars use data-attr:style (Datastar) for width and conditional classes for color:

blade
<div
    data-attr:style="'width: ' + $cpuUsage + '%'"
    data-class:bg-red-500="$cpuUsage > 80"
    data-class:bg-yellow-500="$cpuUsage > 50 && $cpuUsage <= 80"
    data-class:bg-green-500="$cpuUsage <= 50"
    class="h-2 rounded-full">
</div>

The bar width and color update reactively as cpuUsage changes.

Key Takeaways

1. Interval Observer:data-on-interval (Datastar) simplifies periodic polling without managing setInterval manually.

2. Conditional Intervals: Using data-show to control interval elements allows pause/resume functionality.

3. Local Signals for UI State: The _paused signal (Datastar local signal) manages UI state without server communication.

Enhancements

Alert Thresholds

Add visual alerts for critical values:

blade
<div data-show="$cpuUsage > 90" class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6">
    ⚠️ Critical: CPU usage is above 90%
</div>