Settings Page
Setiap panel membawa tiga account page — profile, security, dan appearance — di bawah path panel itu sendiri dan di dalam shell panel tersebut. Ketiganya adalah class Page biasa, sehingga authorization, navigation, dan breadcrumbs bekerja sama seperti page yang Anda buat sendiri. Gunakan dokumentasi ini untuk menonaktifkannya, mempertahankan URL /settings/* milik aplikasi lama, atau membuat settings page sendiri.
Contoh minimal yang berfungsi
Tidak ada yang perlu didaftarkan. Panel yang tidak mengubah konfigurasi settings otomatis memiliki ketiganya:
<?php
declare(strict_types=1);
namespace App\Panels\Admin;
use PandaPanel\Core\Panel;
use PandaPanel\Core\PanelProvider;
final class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel->path('admin')->auth();
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
curl -I https://example.test/admin/settings/profile # 200 for a signed-in adminPanel yang memang tidak membutuhkan account settings — misalnya kiosk atau single-purpose reporting panel — dapat mematikannya:
$panel->settings(false);Tiga built-in page
Ketiganya berada di namespace PandaPanel\Pages\Settings.
| Class | Title | Slug | Path | Inertia component |
|---|---|---|---|---|
ProfileSettings | Profile | settings-profile | {panel}/settings/profile | panel/settings/Profile |
SecuritySettings | Security | settings-security | {panel}/settings/security | panel/settings/Security |
AppearanceSettings | Appearance | settings-appearance | {panel}/settings/appearance | panel/settings/Appearance |
Detail navigation dan route:
| Class | Icon | Group | Sort | Route middleware |
|---|---|---|---|---|
ProfileSettings | user | Account | 10 | — |
SecuritySettings | shield | Account | 20 | Illuminate\Auth\Middleware\RequirePassword |
AppearanceSettings | palette | Account | 30 | — |
Slug hanya satu segment sedangkan path dua segment. Pemisahan ini disengaja: slug digunakan sebagai route name dan registry key, sedangkan routePath() menentukan bentuk URL pada address bar.
public static function routePath(): string // 'settings/profile'Route name mengikuti prefix milik panel, sehingga URL sebaiknya diminta melalui API daripada ditulis manual:
use PandaPanel\Pages\Settings\ProfileSettings;
use PandaPanel\Pages\Settings\SecuritySettings;
ProfileSettings::routeName('admin'); // 'panel.admin.pages.settings-profile'
ProfileSettings::url('admin'); // '/admin/settings/profile'
ProfileSettings::url('app'); // '/app/settings/profile'
SecuritySettings::url('admin'); // '/admin/settings/security'2
3
4
5
6
7
Kedua method menerima Panel|string|null. Jika null, panel di-resolve dari current request; memanggilnya di luar panel tanpa argument melempar exception:
public static function routeName(Panel|string|null $panel = null): string
public static function url(Panel|string|null $panel = null): string2
Setiap panel memiliki route settings miliknya sendiri. Tiga panel berarti tiga route profile page yang masing-masing dirender dalam shell, theme, dan navigation panel tersebut.
Mengaktifkan dan menonaktifkan
public function settings(bool $settings = true): self
public function hasSettings(): bool2
Settings page digabung ke page list panel, bukan diperlakukan sebagai special case di tempat lain. Karena itu discovery, caching, dan route registration menangani ketiganya seperti page biasa:
Panel::make('kiosk')->settings(false)->getPages(); // []
Panel::make('other')->getPages();
// [ProfileSettings::class, SecuritySettings::class, AppearanceSettings::class]2
3
getPages() menempatkan built-in settings page terlebih dahulu, lalu menggabungkan page yang didaftarkan eksplisit, dengan deduplication berdasarkan class.
Data yang dikirim setiap page
Settings page hanya merender UI; page tidak melakukan write. Ini sengaja agar tetap ada tepat satu jalur yang mengubah profile, terlepas dari shell tempat form dikirim.
ProfileSettings mengirim dua props dan tetap POST/PATCH ke ProfileController milik aplikasi:
public function props(): array
{
return [
'mustVerifyEmail' => Auth::user() instanceof MustVerifyEmail,
'status' => session('status'),
];
}2
3
4
5
6
7
SecuritySettings mirror props yang dibangun security screen aplikasi, sehingga two-factor dan passkey component tidak membutuhkan branch khusus panel:
| Prop | Type | Sumber |
|---|---|---|
canManageTwoFactor | bool | Features::canManageTwoFactorAuthentication() |
canManagePasskeys | bool | Features::canManagePasskeys() |
passkeys | array | Passkey user: id, name, authenticator, dan dua tanggal yang sudah dibuat human-readable |
passwordRules | string | PandaPanel\Support\PasswordRules::attribute(), value browser passwordrules yang dibangun dari Password::defaults() |
emailCodeEnabled | bool | PandaPanel\Auth\EmailCodeFactor::isEnabledFor($user) |
emailCodeUrls | array{enable: string, disable: string} | Route two-factor milik panel |
twoFactorEnabled | bool | Hanya ada ketika fitur two-factor Fortify aktif |
requiresConfirmation | bool | Hanya ada ketika fitur two-factor Fortify aktif |
Kedua URL berasal dari route panel sendiri:
route($this->panel()->routeName('auth.two-factor.enable'), absolute: false);
route($this->panel()->routeName('auth.two-factor.disable'), absolute: false);2
Kedua route berada di belakang RequirePassword, sama seperti page-nya. Panel juga mendaftarkan auth.two-factor.challenge, .send, dan .verify untuk emailed code challenge — lihat Email Code Challenge.
AppearanceSettings tidak mengirim props. Theme choice disimpan di local storage dan cookie oleh useAppearance, sehingga tidak ada server state yang perlu dikirim atau disimpan.
Navigation
Ketiga page memakai group Account dengan sort 10, 20, dan 30, sehingga dirender dalam urutan tersebut pada satu heading. Jika panel ingin menempatkan group itu pada posisi tertentu di sidebar, deklarasikan:
$panel->navigationGroups([
'User Management',
'System',
'Account',
]);2
3
4
5
Group yang tidak dideklarasikan panel tetap dirender; declaration menentukan urutan, bukan keberadaan.
Mempertahankan URL settings aplikasi lama
Laravel starter kit biasanya sudah memiliki /settings/profile dan route settings lain. Setelah UI settings dipindahkan ke Panel, pertahankan alamat lama sebagai alias redirect, bukan implementasi kedua, sehingga bookmark dan generated link lama tetap bekerja. Package menyediakan pola ini sebagai contoh, bukan behavior otomatis, karena keputusan routing tetap milik aplikasi:
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Settings;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use PandaPanel\Core\PanelManager;
use PandaPanel\Pages\Page;
final class SettingsRedirectController
{
/**
* @param class-string<Page> $page
*/
private function toPanel(Request $request, string $page): RedirectResponse
{
$panel = app(PanelManager::class)->firstAccessibleTo($request->user());
abort_if($panel === null || ! $panel->hasSettings(), 403);
return redirect($page::url($panel));
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Contoh lengkap berada di examples/app/Http/Controllers/Settings/SettingsRedirectController.php dan di-wire melalui examples/routes/web.php. Dua detail penting untuk ditiru: route GET melakukan redirect sedangkan PATCH tetap menuju ProfileController aplikasi — screen berpindah ke Panel, write path tidak — dan hasSettings() diperiksa agar redirect ke panel yang menonaktifkan settings menghasilkan refusal, bukan 404 yang membingungkan.
Mengustomisasi screen
Vue component settings dipublish ke aplikasi oleh panel:install, atau secara manual:
php artisan vendor:publish --tag=panda-panel-assetsFile berada di resources/js/pages/panel/settings/Profile.vue, Security.vue, dan Appearance.vue. Setelah publish, file menjadi bagian repository dan build aplikasi sehingga bebas diedit. Konsekuensinya package update tidak dapat mengubah file tersebut secara silent, dan panel:assets digunakan untuk melaporkan file yang tertinggal versi:
php artisan panel:assets # which published files are out of dateMembuat settings page sendiri
Settings page hanyalah standalone Page dengan nested routePath(). Tidak ada mekanisme khusus:
php artisan make:panel-page BillingSettings --panel=Admin --component<?php
declare(strict_types=1);
namespace App\Panels\Admin\Pages;
use BackedEnum;
use PandaPanel\Pages\Page;
final class BillingSettings extends Page
{
protected static ?string $title = 'Billing';
protected static ?string $subheading = 'Plan, invoices, and payment method.';
protected static ?string $slug = 'settings-billing';
protected static string $component = 'Panels/Admin/Pages/BillingSettings';
protected static ?string $navigationIcon = 'receipt';
protected static string|BackedEnum|null $navigationGroup = 'Account';
protected static int $navigationSort = 40;
/**
* One segment as a slug, two in the address bar — the same split the
* built-in settings pages use.
*/
public static function routePath(): string
{
return 'settings/billing';
}
public static function canAccess(): bool
{
return auth()->user()?->can('manage-billing') ?? false;
}
/**
* @return array<string, mixed>
*/
public function props(): array
{
return ['plan' => 'pro'];
}
}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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Member base class yang digunakan:
| Member | Signature atau type | Tujuan |
|---|---|---|
$title | ?string | Fallback ke headline dari class name |
$heading | ?string | Fallback ke title() |
$subheading | ?string | Baris di bawah heading |
$slug | ?string | Route name dan registry key; fallback ke kebab-case class name |
$component | string | Default panel/Page, generic renderer |
$navigationIcon, $activeNavigationIcon | ?string | Icon registry key |
$navigationGroup | string|BackedEnum|null | Sidebar heading |
$navigationSort | int | Urutan dalam group |
$shouldRegisterNavigation | bool | false menyembunyikan navigation entry; route tetap ada |
$middleware | list<string> | Ditambahkan ke route page |
routePath() | static function (): string | URL, default ke slug |
canAccess() | static function (): bool | Diterapkan oleh route, bukan hanya navigation |
props() | function (): array<string, mixed> | Serializable page props |
widgets() | function (): list<class-string<Widget>> | Widget yang dirender di page |
breadcrumbs() | function (): list<Breadcrumb> | Default dashboard → group → title |
headerActions() | function (): list<array<string, mixed>> | Button di samping heading |
Jika $component dibiarkan default, page dirender menggunakan generic shell. Ini cukup untuk page yang isinya widget atau daftar value. Atur custom component ketika page perlu menggambar form atau UI khusus.
Page mendaftarkan route GET saja. Settings page yang menyimpan data melakukan POST/PATCH ke route milik aplikasi, seperti built-in profile page:
public function headerActions(): array
{
return [[
'name' => 'manage-plan',
'label' => 'Manage plan',
'icon' => 'link',
'variant' => 'default',
'type' => 'link',
'url' => route('billing.portal'),
'confirmation' => null,
]];
}2
3
4
5
6
7
8
9
10
11
12
Mengganti built-in page
Page slug harus unik di dalam satu panel. Mendaftarkan page sendiri dengan slug settings-profile ketika built-in settings masih aktif melempar PanelRegistrationException::duplicatePageSlug() saat boot. Failure dibuat loud karena dua page yang mengklaim route yang sama adalah mistake, bukan override. Matikan built-in page terlebih dahulu, lalu daftar ulang account page yang memang diinginkan:
$panel
->settings(false)
->pages([
App\Panels\Admin\Pages\ProfileSettings::class,
App\Panels\Admin\Pages\BillingSettings::class,
]);2
3
4
5
6
settings(false) bersifat all-or-nothing: ketiga built-in page dihapus sekaligus. Jika hanya ingin mengganti satu dan mempertahankan dua lainnya, Anda harus mendaftarkan kembali dua page yang dipertahankan.
Catatan
- Route settings hanya GET. POST ke
/admin/settings/profilemenghasilkan 405. Write endpoint tetap milik aplikasi dan Fortify. - Security page melakukan redirect, bukan refusal.
RequirePasswordberada pada route, bukancanAccess(), karena stale session harus diarahkan ke password confirmation sedangkancanAccess()hanya dapat menjawab yes/no. Session yang belum dikonfirmasi diarahkan keroute('password.confirm'). - Panel access diperiksa lebih dulu. User yang ditolak panel menerima 403 pada
/admin/settings/profileseperti page lain; guest diarahkan ke login, termasuk login panel sendiri jika tersedia. settings(false)menghapus route. Setelah ituProfileSettings::url($panel)tidak memiliki route untuk dibangun dan akan melempar exception. Karena itu contoh redirect memeriksahasSettings()sebelum membangun URL.- Appearance bersifat per-device, bukan per-user. State berada di local storage dan cookie, tidak disimpan server-side, sehingga pilihan tidak mengikuti user ke browser lain.
- Security page mengikuti feature Fortify yang tersedia.
twoFactorEnableddanrequiresConfirmationtidak ada ketika two-factor nonaktif, sedangkanpasskeyskosong ketika user model bukanPasskeyUser. Page tetap dapat dirender. - Behavior ini dipin oleh
tests/Feature/Panel/PanelSettingsTest.php.