Polling
Polling widget memperbarui dirinya sendiri pada interval tertentu tanpa tindakan dari pengguna. Gunakan polling ketika nilai dapat berubah saat dashboard sedang dilihat — misalnya queue depth, jumlah active session, atau order dalam satu jam terakhir — dan data yang stale dapat menyesatkan, bukan sekadar sedikit terlambat.
Polling nonaktif secara default. Setiap interval berarti satu request untuk setiap tab yang sedang terbuka. Biaya tersebut masuk akal untuk queue depth, tetapi tidak masuk akal untuk total yang hanya berubah dua kali sehari. Karena itu polling diaktifkan secara opt-in per widget, bukan sebagai satu setting global yang mudah diaktifkan lalu terlupakan.
Contoh minimal yang dapat langsung digunakan
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Widgets;
use Illuminate\Support\Facades\DB;
use PandaPanel\Widgets\StatsWidget;
use PandaPanel\Widgets\Support\Stat;
final class QueueDepth extends StatsWidget
{
protected static ?int $pollingInterval = 15;
protected static ?string $heading = 'Queue';
/**
* @return list<Stat>
*/
public function stats(): array
{
return [
Stat::make('Waiting', DB::table('jobs')->count()),
];
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Setiap lima belas detik page melakukan reload terhadap widget props dan nilai pada widget diperbarui di tempat.
API
| Member | Signature | Default |
|---|---|---|
$pollingInterval | protected static ?int | null |
pollingInterval() | public static function pollingInterval(): ?int | $pollingInterval |
use App\Panels\Admin\Widgets\QueueDepth;
QueueDepth::pollingInterval(); // 152
3
Nilainya menggunakan satuan detik. null berarti polling tidak aktif, begitu juga value <= 0 — frontend memeriksa kedua kondisi tersebut sebelum memulai timer.
Value diserialisasi ke widget definition dengan key polling:
[
'id' => 'queue-depth',
'polling' => 15, // seconds, or null
// ...
]2
3
4
5
Apa yang sebenarnya dilakukan polling
WidgetShell.vue memulai setInterval ketika widget di-mount dan membersihkannya sebelum unmount. Setiap tick menjalankan Inertia partial reload:
router.reload({ only: props.reloadProps });Props yang diminta oleh polling ditentukan oleh page, bukan oleh widget, karena data widget pada akhirnya merupakan prop milik page tempat widget tersebut berada:
| Page | Props yang di-reload |
|---|---|
Dashboard, standalone Page | ['widgets', 'widgetData'] |
| resource page | ['headerWidgets', 'footerWidgets', 'widgetData'] |
Ada dua konsekuensi dan keduanya disengaja.
Satu polling memperbarui semua widget pada page, bukan hanya widget yang timer-nya sedang berjalan. Props berada pada level page, sehingga me-resolve ulang satu widget berarti me-resolve ulang seluruh set. Jika tiga widget melakukan polling masing-masing setiap 15, 30, dan 60 detik, seluruh set widget dihitung ulang pada setiap tick dari ketiga timer tersebut.
State page lainnya tetap dipertahankan. Opsi only membatasi request pada widget props, dan partial reload mempertahankan client state lain. Karena itu polling tidak membuang filter yang sedang diketik ataupun scroll position pengguna.
Tidak ada endpoint khusus per widget. Endpoint seperti itu tetap harus me-resolve ulang authorization page, filter, dan page context agar dapat menghasilkan data yang benar — dan seluruh pekerjaan tersebut memang menjadi tanggung jawab page.
Memilih interval
| Interval | Cocok untuk |
|---|---|
| 5–15 detik | queue depth, active jobs, live session count |
| 30–60 detik | order hari ini, sign-up, error count |
| 300 detik+ | data yang tidak masalah jika sedikit stale |
null | total keseluruhan, lifetime count, aggregate bulanan |
Biayanya adalah satu request per interval untuk setiap tab yang terbuka, dan seluruh widget props milik page dihitung ulang pada setiap request. Dashboard dengan satu widget berinterval 5 detik berarti melakukan dua belas full widget resolution per menit untuk setiap viewer.
/**
* A minute. These are counts of a table that changes when somebody signs up,
* which is often enough to be worth watching and rare enough that a shorter
* interval would be a request for nothing.
*/
protected static ?int $pollingInterval = 60;2
3
4
5
6
Menggabungkan polling dengan fitur lain
Dengan filter
Polling melakukan reload terhadap URL saat ini, sehingga query string — dan berarti seluruh filter — tetap sama. Widget yang sedang difilter ke "last 7 days" tetap menghitung tujuh hari terakhir pada setiap polling.
Dengan lazy widget
Polling meminta widgetData, yaitu prop tempat payload widget lazy berada. Karena itu lazy payload akan di-resolve ulang pada setiap polling. Widget yang sekaligus lazy dan polling tetap menjalankan slow query setiap interval; dalam banyak kasus sebaiknya pilih salah satunya.
Dengan otorisasi
Seluruh page dirender ulang di server sehingga canView() juga dijalankan kembali. Jika sebuah widget kehilangan izin di tengah session, widget akan hilang pada polling berikutnya daripada terus menampilkan nilai yang sudah tidak seharusnya terlihat.
Dengan table widget
TableWidget menjalankan kembali query() pada setiap polling menggunakan page dan sort yang saat itu ada pada URL. Table widget dengan polling berarti menjalankan query secara periodik; pastikan query tersebut memiliki index yang sesuai.
Testing
Interval adalah bagian dari widget definition, sehingga lakukan assertion pada definition tersebut:
it('polls only the widgets that asked to', function (): void {
$this->actingAs($this->admin)->get('/admin')
->assertInertia(function (AssertableInertia $page): void {
$polling = array_column($page->toArray()['props']['widgets'], 'polling', 'id');
expect($polling['user-stats'])->toBe(60)
->and($polling['recent-users'])->toBeNull();
});
});2
3
4
5
6
7
8
9
Timer itu sendiri merupakan behaviour frontend dan tidak diuji oleh PHP test suite.
Hal yang perlu diperhatikan
- Satuan interval adalah detik, bukan milidetik.
pollingInterval = 500berarti kira-kira sekali setiap delapan menit, bukan dua kali per detik. $pollingIntervalbersifat static. Nilainya tidak dapat berbeda per user atau berdasarkan value filter.- Satu widget yang melakukan polling menyebabkan seluruh widget pada page ikut di-reload. Jangan menempatkan fast poller berdampingan dengan widget yang sangat mahal jika tidak diperlukan.
- Timer menggunakan
setIntervalbiasa. Tidak ada jitter, tidak ada backoff ketika gagal, dan tidak berhenti ketika tab tersembunyi — background tab tetap melakukan polling. - Setiap widget yang di-mount dengan interval memiliki timer sendiri. Dua widget dengan interval 15 detik berarti dua reload setiap 15 detik, bukan satu reload bersama.
- Polling bukan broadcasting. Untuk push update, gunakan integrasi notification dan broadcasting milik panel daripada memaksa interval sangat pendek. Lihat Broadcasting.
- Polling adalah full server render untuk widget props milik page: authorization, filter, dan query semuanya dijalankan kembali. Itulah yang membuat hasilnya benar, dan sekaligus alasan performanya perlu diukur.