Membuka Panel Pertama Anda
Sebuah Panel pada dasarnya terdiri dari satu provider class, satu baris konfigurasi, dan satu URL. Dokumen ini membawa Panel hasil scaffold make:panel sampai menjadi screen yang dapat digunakan untuk sign-in, sekaligus menjelaskan setiap bagian dari provider yang dihasilkan agar Panel berikutnya dibuat dengan perubahan yang disengaja, bukan sekadar copy-paste.
Gambaran Lengkap
php artisan make:panel Admin// config/panda-panel.php
'panels' => [
App\Panels\Admin\AdminPanelProvider::class,
],2
3
4
php artisan panel:user --panel=admin
php artisan serve2
Buka /admin. panel:install menjalankan keempat langkah tersebut untuk Anda; menjalankannya secara manual menghasilkan hasil yang sama, dan biasanya inilah workflow yang digunakan saat membuat Panel kedua.
Apa yang Ditulis make:panel
<?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')
->name('Admin')
->icon('layout-grid')
->auth()
->discoverResources(app_path('Panels/Admin/Resources'))
->discoverPages(app_path('Panels/Admin/Pages'))
->discoverWidgets(app_path('Panels/Admin/Widgets'));
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app/Panels/Admin/
├── AdminPanelProvider.php
├── Pages/.gitkeep
├── Resources/.gitkeep
└── Widgets/.gitkeep2
3
4
5
File .gitkeep bukan sekadar hiasan. Discovery memindai directory tersebut, sedangkan Git tidak melacak directory kosong. Tanpa .gitkeep, provider dapat menunjuk ke path yang hilang setelah repository di-clone.
php artisan make:panel Admin --path=back-office # a URL prefix that is not the kebab-cased name
php artisan make:panel Admin --force # overwrite an existing provider2
ID Panel
public function panelId(): string; // AdminPanelProvider → 'admin'
public function build(): Panel; // $this->panel(Panel::make($this->panelId()))2
PandaPanel\Core\PanelProvider menurunkan ID dari basename class: suffix PanelProvider dihapus, lalu sisanya diubah menjadi kebab-case. ID ini digunakan oleh seluruh route name, parameter middleware, dan option --panel=. Gunakan ->id('something-else') jika ingin menggantinya.
Hindari melakukan service resolution di dalam panel(). Method tersebut berjalan pada saat provider boot, sebelum container sepenuhnya siap untuk request-scoped binding.
Method yang Ada pada Stub
| Call | Signature | Efek |
|---|---|---|
path | path(string $path): self | Prefix URL, dengan slash di awal/akhir dibuang. Jika tidak dipanggil, default-nya adalah Panel ID. |
name | name(string $name): self | Nama yang ditampilkan untuk Panel. Default Str::headline($id). |
icon | icon(?string $icon, ?string $darkIcon = null): self | Nama icon Lucide dari build-time registry. Jalankan panel:icons setelah menambah nama baru. |
auth | auth(bool $verified = true): self | Menambahkan auth dan — kecuali verified: false — verified ke auth middleware Panel. |
discoverResources | discoverResources(string ...$paths): self | Directory yang dipindai untuk subclass Resource. Bersifat akumulatif. |
discoverPages | discoverPages(string ...$paths): self | Sama, untuk subclass Page. |
discoverWidgets | discoverWidgets(string ...$paths): self | Sama, untuk subclass Widget. |
Discovery membaca nama class dari prefix PSR-4 Composer, bukan dengan mem-parsing file. Hanya concrete class yang mengimplementasikan contract yang sesuai yang dimasukkan, kemudian hasilnya diurutkan agar dua mesin menghasilkan manifest yang sama. Explicit registration tetap didukung dan digabungkan dengan hasil discovery:
$panel->resources([\App\Panels\Admin\Resources\Users\UserResource::class]);
$panel->pages([\App\Panels\Admin\Pages\Settings::class]);
$panel->widgets([\App\Panels\Admin\Widgets\UserStats::class]);2
3
Mengembangkan Konfigurasi Panel
Berikut contoh Admin Panel dari example application. Setiap call di dalamnya dapat Anda tambahkan ke stub:
use App\Models\User;
use App\Panels\Admin\Pages\AccountsDashboard;
use Illuminate\Contracts\Auth\Authenticatable;
use PandaPanel\Pages\Dashboard;
return $panel
->path('admin')
->name('Administrator')
->brandName((string) config('app.name'))
->icon('shield')
->sidebar(appearance: 'sidebar')
->auth()
->navigationGroups(['User Management', 'System'])
->dashboards([Dashboard::class, AccountsDashboard::class])
->discoverResources(app_path('Panels/Admin/Resources'))
->discoverPages(app_path('Panels/Admin/Pages'))
->discoverWidgets(app_path('Panels/Admin/Widgets'))
->canAccess(static fn (?Authenticatable $user): bool => $user instanceof User && $user->is_admin);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| Call | Signature | Catatan |
|---|---|---|
brandName | brandName(string $brandName): self | Ditampilkan pada shell. Default config('app.name'). |
navigationGroups | navigationGroups(array $groups): self | Menentukan urutan group. Bersifat akumulatif; group yang tidak dideklarasikan tetap muncul setelahnya. |
sidebar | sidebar(bool $collapsible = true, bool $defaultOpen = true, string $variant = 'sidebar', string $appearance = 'inset'): self | variant: 'header' mengganti side rail menjadi top navigation; appearance dapat berupa inset, floating, atau sidebar. |
dashboard | dashboard(string $page): self | Mengganti Page yang digunakan pada root Panel. |
dashboards | dashboards(array $pages): self | Root Page ditambah dashboard tambahan; semuanya merupakan Page biasa. |
canAccess | canAccess(Closure $callback): self | fn (?Authenticatable $user): bool. Diperiksa pada setiap request yang masuk ke Panel. |
login | login(bool $login = true): self | Memberikan guest route milik Panel sendiri pada path Panel. Default off. |
settings | settings(bool $settings = true): self | Tiga Account Page bawaan. Default on. |
Akses terdiri dari dua pertanyaan dan keduanya harus sama-sama mengizinkan: closure canAccess() milik Panel, serta PanelUser::canAccessPanel() pada User Model jika model mengimplementasikan contract tersebut. User yang sudah sign-in tetapi ditolak akan menerima 403, bukan redirect — menyembunyikan navigation bukan mekanisme access control.
Meregistrasikan Panel
// config/panda-panel.php
'panels' => [
App\Panels\Admin\AdminPanelProvider::class,
App\Panels\Support\SupportPanelProvider::class,
],2
3
4
5
Panel didaftarkan secara eksplisit, bukan ditemukan otomatis, karena dua alasan: application mendeklarasikan seluruh panelnya di satu tempat, dan penambahan Panel seharusnya merupakan perubahan yang disengaja, bukan side effect filesystem. Ketika request tidak menyebut Panel tertentu, PandaBear berjalan berdasarkan id Panel, bukan urutan config. Class di dalam Panel tetap dapat ditemukan melalui discovery.
make:panel hanya mencetak baris config yang diperlukan. panel:install yang menuliskannya — lihat Running panel:install untuk empat outcome yang mungkin terjadi.
Dua Panel tidak boleh memiliki ID yang sama dan tidak boleh menggunakan path yang sama pada domain yang sama:
$panel->domain('admin.example.test'); // then two panels may both use path 'admin'Kedua collision tersebut melempar PandaPanel\Exceptions\PanelRegistrationException pada saat boot, bukan membiarkan satu route diam-diam menimpa route lainnya.
URL yang Sekarang Tersedia
Route name mengikuti pola panel.{id}.*, sehingga Wayfinder output dan server-side URL generation tetap dapat diprediksi. Untuk Panel dengan ID admin pada path admin:
| URL | Route Name | Fungsi |
|---|---|---|
/admin | panel.admin.dashboard | Root Panel, merender Page Dashboard |
/admin/search | panel.admin.search | Global search, mengembalikan JSON |
/admin/options | panel.admin.options | Mengambil row tambahan untuk searchable select yang tidak muat pada first page |
/admin/uploads | panel.admin.uploads | Upload milik form field; file disimpan sebelum form disubmit |
/admin/form-state | panel.admin.form-state | Menghasilkan bentuk terbaru live form. Tidak melakukan validation atau write |
/admin/exports/{file} | panel.admin.export-file | File export yang selesai, hanya dapat diakses user yang membuatnya |
/admin/imports/{file} | panel.admin.import-file | Row yang tidak dapat diterima ketika import |
/admin/notifications | panel.admin.notifications.index | Notification Centre yang di-scope ke signed-in user |
/admin/actions/* | panel.admin.actions.* | record, bulk, reorder, cell, table, infolist, form (GET), dan submit (POST ke path yang sama) |
/admin/relations/* | panel.admin.relations.* | form, save, action, bulk |
/admin/settings/profile | panel.admin.pages.settings-profile | Account Page, kecuali settings(false) |
/admin/settings/security | panel.admin.pages.settings-security | Dilindungi RequirePassword |
/admin/settings/appearance | panel.admin.pages.settings-appearance | Appearance Settings |
/admin/{slug} | panel.admin.resources.{slug}.index | List Page Resource, dengan create, view, edit di bawahnya |
/admin/{page-slug} | panel.admin.pages.{slug} | Standalone Page |
/admin/login | panel.admin.auth.login | Hanya jika ->login() aktif. Diregistrasikan di luar auth middleware Panel |
Setiap route menunjuk ke controller, bukan closure, sehingga php artisan route:cache tetap dapat digunakan.
php artisan route:list --name=panel.adminApa yang Dirender
/admin menggunakan PandaPanel\Pages\Dashboard: sebuah Page biasa yang mengambil widget dari registry milik Panel, bukan dari list tersendiri. Karena itu metadata, breadcrumb, header action, authorization, dan lazy widget berperilaku sama seperti pada Page lainnya.
Vue component-nya adalah panel/Dashboard, dipublish ke resources/js/pages/panel/Dashboard.vue. Navigation dibangun per request dari registry Panel — tidak ada hard-coded array — sehingga Panel yang belum menemukan Resource/Page/Widget apa pun tetap merender shell, Dashboard, dan Account Page, tetapi tidak menampilkan navigation lain.
Menambahkan Fitur ke Panel
php artisan make:panel-resource Product --panel=Admin
php artisan make:panel-page Reports --panel=Admin
php artisan make:panel-widget Revenue --panel=Admin --type=stats2
3
Semua class tersebut ditemukan melalui path discovery yang sudah ada pada provider; tidak perlu registration tambahan. Resource yang baru dibuat akan 403 sampai model-nya memiliki policy. Authorization gate ditanya dan menjawab tidak, dan itu adalah default yang disengaja. Pada development, Panel akan mencatat model yang belum memiliki policy sekaligus menyebut command make:policy.
php artisan make:policy ProductPolicy --model=ProductCatatan
- 404 pada URL Panel hampir selalu berarti provider belum terdaftar di
panels. Keberadaan provider class saja tidak cukup; tidak ada proses yang mencari provider tersebut secara otomatis. - 403 berarti salah satu dari dua access rule menjawab tidak, bukan berarti route hilang. Periksa
canAccess()terlebih dahulu, kemudiancanAccessPanel()pada User Model. path()default ke Panel ID.Panel::make('admin')tanpapath()akan menjawab pada/admin.- Panel provider yang class-nya sudah tidak ada akan dilewati, bukan membuat aplikasi fatal. Fatal saat boot akan menjatuhkan seluruh route, termasuk route yang seharusnya membantu menunjukkan masalah.
panel:cachemelaporkan daftar yang benar-benar ditemukan pada tempat yang lebih mudah diperiksa. - Setelah
panel:cache, discovery tidak dijalankan lagi. Resource yang ditambah setelah cache dibuat tidak memiliki route, navigation entry, maupun error. Manifest menyimpan fingerprint discovery path dan pada development memberi warning jika stale. Solusinya adalahphp artisan panel:clear.
Lihat Juga
- Directory structure — lokasi seluruh file yang dibuat Panel
- Creating the first user — membuat account yang benar-benar dapat sign-in
- Common install problems
- Panels: defining panels, ids, paths and domains, access, dashboards
- Resources: creating resources
- Widgets, Custom pages
- Concepts: routing, discovery, caching
- CLI: make:panel, panel:cache
- Troubleshooting: panel routes 404