Ikhtisar fitur
Semua yang dikirim paket ini, disebut per nama kelas, supaya Anda bisa langsung tahu apakah sesuatu memang ada sebelum berkeliling mencari halamannya. Setiap bagian menautkan ke panduan yang mendokumentasikannya secara benar. Kalau sebuah kelas tidak disebut di sini, kelas itu tidak ada di dalam paket.
Panel terkecil yang sudah lengkap
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')
->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
Didaftarkan di config/panda-panel.php di bawah panels. Semua yang ada di bawah ini dijangkau dari satu kelas itu.
Panel
PandaPanel\Core\Panel, dikonfigurasi lewat PandaPanel\Core\PanelProvider::panel().
| Kelompok | Metode |
|---|---|
| Identitas | id, name, path, domain |
| Akses | middleware, authMiddleware, auth, canAccess, requireTwoFactor |
| Pintu depan | login, registration, passwordReset, emailVerification |
| Registrasi | resources, pages, widgets, discoverResources, discoverPages, discoverWidgets |
| Presentasi | navigationGroups, brandName, brandLogo, darkBrandLogo, icon, darkIcon, favicon, darkFavicon, darkMode, colors, cssHooks, maxContentWidth |
| Shell | sidebar, topNavigation, sidebarWidth, collapsedSidebarWidth, navigation, topbar, breadcrumbs, sidebarComponent, topbarComponent, userMenuItems |
| Halaman utama | dashboard, dashboards |
| Bawaan | settings, notifications, broadcasting, globalSearch |
| Perilaku | databaseTransactions, strictAuthorization, unsavedChangesAlerts, bootUsing, configureActions |
| Perilaku navigasi | prefetch, fullPageUrls, errorNotification, hideErrorNotification |
| Perluasan | renderHook, subNavigationPosition, assets, plugins |
| Tenancy | tenant, tenantUrlUsing |
Pembacanya berawalan get (getId(), getPath(), getSidebar()); setter-nya memakai nama polos. Path discovery, grup navigasi, aset, dan render hook sifatnya menumpuk, bukan menimpa.
$panel
->databaseTransactions() // menyala secara default
->strictAuthorization() // mati secara default
->unsavedChangesAlerts() // menyala secara default
->prefetch('hover') // 'hover' (default), 'mount', 'click', atau false
->sidebar(collapsible: true, defaultOpen: true, variant: 'sidebar', appearance: 'inset')
->globalSearch(enabled: true, limit: 50, debounce: 300, keyBindings: ['mod+k'])
->errorNotification(403, 'Not allowed', 'Ask an administrator.')
->hideErrorNotification(404);2
3
4
5
6
7
8
9
→ Mendefinisikan Panel, API Panel
Resource
PandaPanel\Resources\Resource. Satu kelas per model per panel.
| Anggota | Signature |
|---|---|
| model | protected static string $model |
| slug, label | protected static ?string $slug, $navigationLabel, $pluralLabel |
| navigasi | $navigationIcon, $navigationGroup, $navigationSort, $activeNavigationIcon, $cluster |
| eager load | protected static array $with |
| soft delete | protected static bool $softDeletes |
| tenancy | protected static ?string $tenantRelationship |
| pencarian global | protected static array $globalSearchAttributes, $globalSearchLimit, $globalSearchSort |
| skema | table(TableSchema): TableSchema, form(FormSchema): FormSchema, infolist(InfolistSchema): InfolistSchema |
| routing | pages(): array, relationManagers(): array, integrations(Integrations): Integrations |
| query | query(): Builder, globalSearchQuery(): Builder |
| pencarian record | resolveRecord($key): Model, findRecord($key): ?Model, findRecords(array $keys): Collection |
| url | url(string $page = 'index', $record = null, $panel = null, $parent = null): string, routeName(string $page = 'index', $panel = null): string |
| otorisasi | canViewAny, canView, canCreate, canEdit, canDelete, canDeleteAny, canRestore, canRestoreAny, canForceDelete, canForceDeleteAny |
PandaPanel\Resources\ResourceConfiguration::for(UserResource::class) mengonfigurasi ulang satu kelas per panel: slug, label, pluralLabel, navigationLabel, navigationGroup, navigationIcon, navigationSort, registerNavigation, modifyQueryUsing.
Halaman resource, semuanya di PandaPanel\Resources\Pages: ListRecords, CreateRecord, ViewRecord, EditRecord, ManageRelatedRecords, dan basis ResourcePage.
→ Membuat Resource, Halaman Resource
Tabel
PandaPanel\Tables\TableSchema, ditambah TableQuery untuk state yang dikendalikan URL dan ArrayTableData untuk tabel atas data yang tidak berada di database.
Kolom — PandaPanel\Tables\Columns\*:
| Kelas | Merender |
|---|---|
TextColumn | teks, dengan formatUsing, tooltip, url |
NumberColumn | angka yang sudah diformat |
BadgeColumn | badge terpetakan lewat colors() dan labels() |
BooleanColumn | tanda ya/tidak |
DateColumn, DateTimeColumn | tanggal yang sudah diformat |
ImageColumn | gambar, dengan circular() |
IconColumn | ikon dari registry |
ColorColumn | contoh warna |
CustomColumn | komponen Vue Anda sendiri |
ToggleColumn, CheckboxColumn, TextInputColumn, SelectColumn | sel yang bisa diedit, meng-extend EditableColumn yang abstrak |
Filter — PandaPanel\Tables\Filters\*: SelectFilter, TernaryFilter, BooleanFilter, DateFilter, FormFilter, QueryBuilderFilter, TrashedFilter, dan Filter yang abstrak. Filter query builder disusun dari Constraints\{TextConstraint, NumberConstraint, DateConstraint, BooleanConstraint}.
Ringkasan — PandaPanel\Tables\Summaries\*: Count, Sum, Average, Range, dan Summarizer yang abstrak. Grouping adalah PandaPanel\Tables\Group; tab filter adalah PandaPanel\Tables\Tab.
Enum — PandaPanel\Tables\Enums\*: ColumnType, BadgeColor, Alignment, ColumnPin, FilterType, ConstraintOperator, RecordActionsPosition, RelationshipAggregate, SortDirection.
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\Enums\SortDirection;
use PandaPanel\Tables\Filters\TernaryFilter;
$table
->columns([
TextColumn::make('name')->searchable()->sortable()->toggleable(false),
TextColumn::make('email')->searchable(),
])
->filters([TernaryFilter::make('verified')])
->defaultSort('created_at', SortDirection::Descending);2
3
4
5
6
7
8
9
10
11
State-nya hidup di query string: search, sort, direction, perPage, page, filters[...], columns, group, tab. Skemanya adalah whitelist; apa pun yang tidak dikenali diabaikan.
→ Dasar TableSchema, Kolom, Filter
Form
PandaPanel\Forms\FormSchema.
Field — PandaPanel\Forms\Components\*, semuanya meng-extend Field yang abstrak:
| Teks dan angka | Pilihan | Tanggal dan waktu | Kaya | Komposit |
|---|---|---|---|---|
TextInput | Select | DatePicker | RichEditor | Repeater |
Textarea | Radio | DateTimePicker | MarkdownEditor | Builder |
NumberInput | Checkbox | TimePicker | CodeEditor | KeyValue |
PasswordInput | CheckboxList | FileUpload | TagsInput | |
HiddenInput | Toggle | ColorPicker | CustomField | |
ToggleButtons | Slider |
Layout — PandaPanel\Forms\Layouts\*: Section, Grid, Tabs, Tab, Wizard, Step, Callout, EmptyState, Relationship, CustomComponent.
Prime (hanya tampilan) — PandaPanel\Forms\Prime\*: Text, Icon, Image.
Enum — PandaPanel\Forms\Enums\*: FieldType, CalloutTone, CodeLanguage, ConditionOperator.
Setiap field memisahkan rendering, validasi, dan penyimpanan:
use PandaPanel\Forms\Components\TextInput;
TextInput::make('name')
->label('Full name')
->required()
->maxLength(255)
->rules(['alpha_dash'])
->hiddenOn(['view'])
->live(onBlur: true)
->dehydrateTo('display_name');2
3
4
5
6
7
8
9
10
→ Dasar FormSchema, Validasi, Siklus Hidup State
Infolist
PandaPanel\Infolists\InfolistSchema, dirender oleh ViewRecord.
Entry — PandaPanel\Infolists\Components\*, meng-extend Entry yang abstrak: TextEntry, BadgeEntry, BooleanEntry, DateTimeEntry, KeyValueEntry, IconEntry, ImageEntry, ColorEntry, CodeEntry, RepeatableEntry, CustomEntry.
Layout — PandaPanel\Infolists\Layouts\*: Section, Grid, Tabs, Tab. Diskriminannya adalah PandaPanel\Infolists\Enums\EntryType.
Action
PandaPanel\Actions\Action dan action bawaan di sebelahnya.
| Jenis | Kelas |
|---|---|
| CRUD | CreateAction, ViewAction, EditAction, DeleteAction, ReplicateAction |
| Soft delete | RestoreAction, ForceDeleteAction |
| Bulk | DeleteBulkAction, RestoreBulkAction, ForceDeleteBulkAction |
| Data | ImportAction, ExportAction |
| Relasi | PandaPanel\Actions\Relations\{AttachAction, DetachAction, DetachBulkAction, AssociateAction, DissociateAction, CreateRelatedAction, EditRelatedAction, DeleteRelatedAction, RestoreAction, RestoreBulkAction, ForceDeleteAction, ForceDeleteBulkAction} |
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Actions\Action;
use PandaPanel\Actions\Enums\ActionVariant;
Action::make('approve')
->label('Approve')
->icon('check')
->variant(ActionVariant::Default)
->requiresConfirmation(heading: 'Approve this record?')
->successMessage('Record approved.')
->action(static fn (Model $record) => $record->approve());2
3
4
5
6
7
8
9
10
11
Ada tiga jenis: tautan (->url()), callback (->action()), dan form (->schema()) yang dialognya diambil saat dibuka. Perilaku modal ada di PandaPanel\Actions\Support\Modal. Enum: ActionType, ActionVariant, ModalWidth, SpreadsheetFormat.
→ Action, Modal, Form Action
Relasi
PandaPanel\Resources\RelationManager, dengan RelationTable dan RelationForm untuk kedua skemanya, serta ManageRelatedRecords untuk relasi yang punya halaman sendiri. Sebuah resource juga bisa disarangkan di bawah resource lain lewat parentResource(), dan dalam hal itu ResolveParentRecord mem-binding induknya sebelum controller mana pun berjalan.
→ Relation Manager, Resource Bersarang vs Relation Manager
Widget
Empat jenis, semuanya meng-extend PandaPanel\Widgets\Widget:
| Jenis | Kelas basis | Mengimplementasikan |
|---|---|---|
| statistik | StatsWidget | stats(): list<Stat> |
| tabel | TableWidget | table(TableSchema), query(): Builder |
| grafik | ChartWidget | labels(): list<string>, series(): list<ChartSeries> |
| kustom | CustomWidget | $component, data(): array |
Kelas pendukung — PandaPanel\Widgets\Support\*: Stat, ChartSeries, ChartOptions, ColumnSpan, WidgetFilters. Enum: ChartVariant, StatColor, WidgetType.
use PandaPanel\Widgets\StatsWidget;
use PandaPanel\Widgets\Support\Stat;
use PandaPanel\Widgets\Enums\StatColor;
final class UserStats extends StatsWidget
{
protected static int $sort = 10;
protected static int|string|array $columnSpan = ['default' => 1, 'md' => 2];
/** @return list<Stat> */
public function stats(): array
{
return [
Stat::make('Total users', User::query()->count())->icon('users'),
Stat::make('Verified', User::query()->whereNotNull('email_verified_at')->count())
->color(StatColor::Success)
->chart([4, 9, 7, 12, 18, 21]),
];
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
canView() diperiksa sebelum data() berjalan, jadi widget yang tidak diotorisasi tidak pernah mengeksekusi query. Grafik digambar dengan SVG inline tanpa dependensi; tidak ada pustaka charting yang dipasang.
Halaman dan navigasi
PandaPanel\Pages\Page adalah halaman mandiri: tanpa record, tanpa tabel. PandaPanel\Pages\Dashboard adalah akar panel bawaan, dan dashboards([...]) mendaftarkan lebih banyak. Tiga halaman akun di PandaPanel\Pages\Settings — ProfileSettings, SecuritySettings, AppearanceSettings — ikut di setiap panel kecuali settings(false) menyatakan sebaliknya.
PandaPanel\Clusters\Cluster mengelompokkan resource dan page di bawah satu prefix path tanpa mengubah nama route mana pun. PandaPanel\Enums\RenderHook menyebut delapan titik penyisipan, SubNavigationPosition menyebut tiga posisi sub-navigasi record, dan ClusterPosition menentukan di mana bar milik cluster berada.
→ Halaman Kustom, Cluster, Render Hook
Autentikasi
Fortify memegang setiap endpoint auth. Sebuah panel boleh menumbuhkan pintu depannya sendiri di path-nya sendiri, membawa mereknya sendiri, dan form-nya tetap mengirim POST ke route Fortify:
$panel->login()->registration()->passwordReset()->emailVerification()->requireTwoFactor();Kode lewat email sebagai faktor kedua adalah PandaPanel\Auth\{EmailCodeChallenge, EmailCodeFactor}, ditegakkan oleh middleware RequireEmailCode. RequireTwoFactor menegakkan requireTwoFactor() milik panel. PandaPanel\Contracts\PanelUser adalah kontrak opsional pada model user — aturan tentang akun, ditanyakan berdampingan dengan canAccess milik tiap panel, dan keduanya harus setuju.
php artisan panel:user membuat akun yang bisa masuk.
→ Fortify, Dua Faktor, Model User
Tenancy
use Illuminate\Http\Request;
$panel->tenant(Team::class, fn (Request $request) => Team::query()
->where('slug', $request->route('team'))
->first());
$panel->tenantUrlUsing(fn (Team $team) => "https://{$team->slug}.example.com/app");2
3
4
5
6
7
Sebuah resource ikut serta dengan menyebutkan relasi yang menuju tenant (protected static ?string $tenantRelationship). Model user mengimplementasikan PandaPanel\Contracts\HasPanelTenants; model tenant boleh mengimplementasikan PandaPanel\Contracts\PanelTenant. PandaPanel\Tenancy\Tenancy adalah pintu masuk di luar request. Middleware ResolveTenant hanya ditambahkan ke panel yang mendeklarasikan tenancy.
→ Konsep Tenancy, Scope Resource
Notifikasi
PandaPanel\Notifications\Notification membangun satu notifikasi; NotificationAction menambahkan tombol padanya; PandaPanel\Broadcasting\PanelNotification mengirimkannya dari mana saja, termasuk dari queued job. PanelDatabaseNotification adalah bentuk tersimpan yang dibaca lonceng notifikasi, dan ShareFlashToast memetakan kunci flash konvensional Laravel ke kanal toast tunggal yang sama.
PanelNotification::dispatch($user, 'Export finished', 'success');Endpoint loncengnya adalah panel.{id}.notifications.{index,read,clear}. Broadcasting menyala secara default dan tidak memakan koneksi sampai ada komponen yang berlangganan; broadcasting(false) mematikannya.
→ Toast, Pusat Notifikasi, Broadcasting
Pencarian global
Palette perintah di atas setiap halaman panel, dibuka dengan mod+k. Diaktifkan per resource:
protected static array $globalSearchAttributes = ['name', 'email', 'author.name'];PandaPanel\Search\{GlobalSearch, GlobalSearchResult} yang mengerjakannya, menjawab JSON di panel.{id}.search. Atribut bertitik akan mencari ke relasi yang disebutnya, dan daftar atributnya adalah whitelist, jadi tidak ada apa pun dari request yang sampai ke nama kolom. Palette-nya tidak muncul ketika panel mematikannya atau ketika tidak ada resource yang ikut serta.
→ Pencarian, Resource yang Bisa Dicari
Impor dan ekspor
ExportAction::make(UserExporter::class, UserResource::class); // daftar yang sudah terfilter
ExportAction::bulk(UserExporter::class, UserResource::class); // baris yang dipilih
ImportAction::make(UserImporter::class, UserResource::class);2
3
PandaPanel\Actions\Exports\{Exporter, ExportColumn, ExportRun} dan PandaPanel\Actions\Imports\{Importer, ImportColumn, ImportRun}. Di atas ambang Exporter::queueAfter(), pekerjaannya berjalan sebagai PandaPanel\Jobs\{RunPanelExport, RunPanelImport} dan tiba sebagai notifikasi berisi tautan unduh. CSV dan XLSX dibaca serta ditulis oleh PandaPanel\Support\Spreadsheet\{Csv, Xlsx} tanpa dependensi spreadsheet. Berkasnya mendarat di disk privat dalam direktori per pengguna.
Integrasi
HTTP keluar yang dipicu oleh operasi tulis sebuah resource, dikonfigurasi saat runtime di layar yang diikutsertakan resource itu lewat integrations()->isEnabled(true). PandaPanel\Integrations\* memuat PanelIntegration, IntegrationObserver, IntegrationDispatcher, IntegrationSignature, IntegrationTemplate, Trigger, OutboundUrl, dan PanelIntegrationDelivery; PandaPanel\Jobs\SendPanelIntegration yang mengirimkannya.
Dua gerbang menjaga setiap tujuan, keduanya di config/panda-panel.php: integrations.allowed_hosts kosong secara default, jadi tidak ada yang bisa dijangkau sampai sebuah tujuan ditambahkan, dan integrations.block_private_networks menolak host yang me-resolve ke rentang privat, loopback, atau link-local.
Plugin
PandaPanel\Plugins\Plugin, dengan PluginMetadata dan PluginCompatibility. Sebuah plugin melakukan persis apa yang bisa dilakukan panel provider, tidak lebih.
$panel->plugins([ReportingPlugin::make()->withCharts()]);| Fase | Kapan | Apa yang pantas di sana |
|---|---|---|
register(Panel $panel): void | saat panel sedang dikonfigurasi | resource, page, widget, grup navigasi |
boot(Panel $panel): void | setelah panel di-resolve, per request | apa pun yang butuh container, pengguna, atau URL |
publishes(): array | hanya saat panel:publish | berkas yang disalin plugin ke dalam aplikasi |
→ Konsep Plugin, Membuat Plugin
Frontend
Dipublikasikan ke dalam aplikasi saat instalasi: resources/js/panel/** (layout, komponen, renderer, composable, registry, tipe), resources/js/pages/panel/** (halaman generik milik framework), dan resources/js/pages/Panels/** (halaman, widget, kolom, field, dan modal milik Anda). resources/css/panda-panel.css adalah stylesheet Tailwind 4-nya.
Komponen kustom di-resolve lewat allowlist import.meta.glob saat build atas resources/js/pages/Panels/**, per direktori: Columns/, Fields/, Schemas/, Entries/, Modals/, Widgets/, Hooks/, Shell/, EmptyStates/. Nama yang tidak ikut dikompilasi tidak bisa dijangkau, apa pun kata request-nya.
→ Pohon Komponen, Registry Komponen
Perintah
| Perintah | Kegunaan |
|---|---|
panel:install | mem-publish, men-scaffold panel, mendaftarkan, memverifikasi frontend |
make:panel | panel provider beserta direktorinya |
make:panel-resource | resource beserta halaman, tabel, dan form-nya |
make:panel-page | halaman mandiri |
make:panel-widget | widget statistik, tabel, grafik, atau kustom |
make:panel-relation-manager | relation manager, opsional beserta halamannya |
panel:user | akun yang bisa masuk ke sebuah panel |
panel:cache / panel:clear | manifest discovery, terdaftar sebagai hook optimize |
panel:icons | menulis ulang registry ikon dari ikon yang dideklarasikan panel Anda |
panel:assets | komponen ter-publish mana yang tertinggal, dan memperbarui yang aman |
panel:publish | menyalin aset sebuah plugin ke dalam aplikasi |
panel:plugins | apa yang terpasang, di panel mana, versi berapa |
Testing
Fungsi global, di-autoload lewat files milik composer: panelTable(), panelForm(), panelInfolistLabels(), panelRecordActions(), panelTableActions(), panelBulkActions(), panelInfolistActions(), fakePanelNotifications(), assertPanelNotificationSentTo(), assertNoPanelNotifications(), assertPanelNotificationStoredFor(), assertNoPanelNotificationsStoredFor(). Kelas di baliknya adalah PandaPanel\Testing\*.
Catatan
- Kelas basis widget bersifat abstrak, begitu pula
Column,EditableColumn,Field,FormComponent,Entry,InfolistComponent,Filter, danSummarizer.Actionbersifat konkret dan dipakai langsung lewatAction::make(). - Tidak ada routing sisi klien di dalam panel dan tidak ada API SPA terpisah. Setiap layar adalah respons Inertia.
- Grafik tidak punya tooltip, zoom, maupun animasi. Apa pun di luar
ChartOptionsadalahCustomWidget, yang jujur menyatakan dirinya buatan khusus. Select::relationship()ada dan ditutupi oleh tipe serta guard clause alih-alih feature test, karena tidak ada model diexamples/yang punya relasi yang layak dipilih.
Baca juga
- Ringkasan — instalasi dan bentuk sebuah panel
- Kenapa PandaBear — penalaran di balik bentuknya
- Arsitektur sekilas — bagaimana kelas-kelas ini sampai ke sebuah layar
- Batasan dan trade-off — apa yang sengaja tidak ada
- Referensi API