4 · Akun pertama dan login
Tujuan: akun yang benar-benar bisa mencapai /admin, dan pemahaman tentang dua aturan yang menentukan apakah akun itu boleh masuk.
Instalasi baru punya panel, route, dan halaman login — tapi belum punya user. Kebiasaan berikutnya biasanya membuka tinker lalu menulis create() dari ingatan sambil menebak kolom apa saja yang dimiliki model User aplikasi ini. Ada perintah khusus untuk itu.
Lakukan ini
php artisan panel:user Name:
> Ada Lovelace
Email address:
> ada@example.com
Password:
> ********
INFO Created Ada Lovelace <ada@example.com>.
INFO They can sign into the Admin panel at admin.2
3
4
5
6
7
8
9
10
11
12
Untuk mode non-interaktif — skrip provisioning atau CI — ketiga opsi wajib diisi, karena setiap prompt dilewati ketika input tidak interaktif:
php artisan panel:user \
--name="$ADMIN_NAME" \
--email="$ADMIN_EMAIL" \
--password="$ADMIN_PASSWORD" \
--panel=admin \
--no-interaction2
3
4
5
6
--password= tercatat di riwayat shell
Dan juga terlihat di daftar proses selama perintah berjalan. Di mesin yang dipakai bersama, lebih baik pakai prompt atau variabel lingkungan.
Model mana yang dibuat
Bukan App\Models\User, dan itu disengaja. Perintah ini menelusuri konfigurasi auth Anda:
--guard= atau auth.defaults.guard
↓
auth.guards.{guard}.provider
↓
auth.providers.{provider}.model2
3
4
5
Proyek dengan App\Models\Admin di balik guard kedua akan mendapat admin-nya; proyek yang mengganti nama model default akan mendapat model itu. Menebak App\Models\User akan berhasil untuk sebagian besar proyek dan gagal diam-diam — membuat baris di tabel yang salah — justru pada proyek yang paling membutuhkan perintah ini.
Yang ditulisnya:
$user->forceFill([
'name' => $attributes['name'],
'email' => $attributes['email'],
'password' => Hash::make($attributes['password']),
'email_verified_at' => now(),
])->save();2
3
4
5
6
forceFill, bukan create, karena intinya justru bekerja terhadap model yang $fillable-nya belum pernah dilihat perintah ini. email_verified_at diisi karena akun yang dibuat dari konsol — menurut definisinya — sudah diverifikasi oleh orang yang menjalankan perintahnya; membiarkannya null akan langsung menabrak dinding verifikasi email, pada panel yang ->auth()-nya mencakup verified.
Laporan aksesnya
Setelah akunnya ada, perintah ini menanyakan ke panel apakah akun itu boleh masuk. Itu dua pertanyaan terpisah, dan keduanya harus setuju:
// 1. Aturan tentang panelnya, di provider:
->canAccess(static fn (?Authenticatable $user): bool => $user?->is_admin === true)
// 2. Aturan tentang akunnya, di model user:
final class User extends Authenticatable implements PandaPanel\Contracts\PanelUser
{
public function canAccessPanel(PandaPanel\Core\Panel $panel): bool
{
return $this->hasVerifiedEmail();
}
}2
3
4
5
6
7
8
9
10
11
Tidak ada yang bisa melonggarkan yang lain. Karena keduanya tinggal di berkas berbeda, laporannya menyebut yang mana yang menolak:
WARN They cannot reach the Admin panel yet — the panel's own canAccess() says no.
Set whatever that rule reads before signing in.2
Panel hasil scaffold di langkah 3 tidak memanggil keduanya, jadi kedua pertanyaan itu default-nya ya, dan akun baru Anda bisa masuk. Aturan yang sesungguhnya akan Anda tambahkan di langkah 8.
Perintah ini melapor, bukan menolak
Akun yang is_admin-nya belum diisi adalah kondisi antara yang wajar, bukan kesalahan yang harus menghentikan proses. Barisnya tetap dibuat.
Masuk ke panel
php artisan serveBuka http://localhost:8000/admin. Tamu yang membuka URL panel diarahkan ke halaman login milik panel itu bila panelnya punya, dan ke route('login') bila tidak — yang merupakan perilaku bawaan Laravel, jadi ini menambah kasus, bukan menggantikannya. Masuk dengan akun yang baru Anda buat.
Apa yang Anda lihat
/admin | PandaPanel\Pages\Dashboard — halaman biasa yang widget-nya diambil dari registry panel. Masih kosong |
| Sidebar | Dibangun per request dari registry panel. Tidak ada array yang di-hardcode di mana pun, karena itulah isinya masih kosong |
/admin/settings/profile | Halaman akun, aktif secara default. Matikan dengan settings(false) |
/admin/settings/security | Dua faktor dan passkey, di balik konfirmasi password |
| Kotak pencarian | /admin/search, menjawab JSON. Belum menemukan apa pun sampai ada resource yang menyatakan dirinya searchable |
Setiap nama route berbentuk panel.{id}.* — panel.admin.dashboard, panel.admin.actions.record, dan seterusnya. Keteraturan itulah yang membuat Wayfinder dan pembuatan URL di sisi server tetap sejalan.
Pastikan berhasil
Anda sudah masuk dan melihat dasbor kosong di dalam shell milik panel — bukan shell starter kit. Kalau yang muncul justru sidebar aplikasi Anda, itu masalah penetapan layout di app.ts dari langkah 2, bukan masalah autentikasi.
Kalau gagal
| Gejala | Artinya | Perbaikan |
|---|---|---|
404 di /admin | Provider belum ada di config/panda-panel.php | Tambahkan — tidak ada yang memindainya otomatis |
| 403 setelah login | Salah satu dari dua aturan akses menolak | Periksa canAccess() dulu, lalu canAccessPanel() |
Diarahkan ke /login terus-menerus | Guard sesi berbeda dengan guard tempat akun dibuat | php artisan panel:user --guard=web |
| Terjebak di dinding verifikasi email | ->auth() mencakup verified dan email_verified_at masih null | Perintah ini mengisinya; user yang dibuat dengan cara lain mungkin tidak |
| Tidak ada baris laporan akses sama sekali | --panel= menyebut id panel yang tidak ada | Cek dengan php artisan route:list --name=panel. |
Menaikkan hak akun di kemudian hari
Flag hak akses yang mass-assignable berarti siapa pun bisa memberikan hak itu ke dirinya sendiri hanya dengan menambah field pada form post. Jadi jauhkan is_admin dari $fillable, dan tulis secara eksplisit:
php artisan tinkeruse App\Models\User;
User::query()->where('email', 'ada@example.com')->first()
->forceFill(['is_admin' => true])->save();2
3
4
Berikutnya
Panel kosong belum bisa disebut panel. Beri dia sesuatu untuk dikelola.
Baca juga
- Creating the first user — setiap opsi dan kedua mode kegagalannya
- Opening your first panel — provider dan seluruh URL-nya
- Panel access —
canAccess()secara mendetail - Troubleshooting: 403