- Add AdminPanelProvider mounting panel at `/admin` with `is_admin` auth guard - Add `is_admin` boolean column to users table - Add brent_prices and price_predictions tables with appropriate indexes - Add comprehensive admin design spec covering resources, dashboard, navigation, and build order - Configure default panel with amber primary color and standard middleware stack - Add compiled Filament assets (actions.js, app.css)
5.7 KiB
Filament Admin Panel — Design Spec
Date: 2026-04-04
Stack: Laravel 11, Filament v5, Livewire v4
Scope: Admin panel for FuelAlert — internal tooling only, no user-facing exposure
1. Access & Auth
- Filament panel mounted at
/admin - Protected by
is_adminboolean onuserstable - Migration adds
is_admin TINYINT(1) DEFAULT 0tousers AdminPanelProvideruses->authGuard('web')(same guard as the main app — no separate admin users table)canAccessPanel()check:$user->is_admin === trueuovidiu@sent.comseeded as admin via a dedicatedAdminSeeder(idempotent — safe to re-run)- No self-registration on the admin panel — access is granted only via the
is_adminflag
2. Dashboard Page
Default landing page. Four StatsOverviewWidget cards:
| Stat | Source | Alert |
|---|---|---|
| Total users | users count |
— |
| Stations in DB | stations count + max(last_seen_at) |
— |
| Latest oil prediction | Most recent price_predictions row — direction, confidence, source |
Yellow if > 24h old |
| API errors (24h) | api_logs where status_code >= 400 or error IS NOT NULL, last 24h |
Red if > 0 |
No charts on the dashboard — keep it scannable. Charts live inside individual resources.
3. Resources
3.1 Users (UserResource)
Purpose: View all registered users, manage admin flag, correct postcodes.
Table columns: name, email, postcode, is_admin (badge), created_at
Filters: is_admin toggle
Actions:
- Edit:
is_admintoggle,postcodetext field (no other fields editable from admin) - Delete: allowed (hard delete, with confirmation modal)
Notes:
- No subscription tier column yet — add when Cashier is integrated
- No impersonation in v1 — add later when user dashboard exists
3.2 API Logs (ApiLogResource)
Purpose: Primary debugging tool — see every outbound HTTP call made by the scheduler.
Table columns: service (badge), method, url (truncated), status_code (colour-coded), duration_ms, error (truncated), created_at
Filters: service (select: fuel_finder, fred, anthropic), errors only (toggle: where error IS NOT NULL or status >= 400), date range
Default sort: created_at DESC
Actions: View (modal showing full url, full error, request/response if available) — no edit, no delete
Colour coding for status_code:
- 2xx → success (green)
- 4xx → warning (yellow)
- 5xx / null → danger (red)
3.3 Oil Predictions (OilPredictionResource)
Purpose: Verify the daily oil:predict job is running and producing sensible output.
Table columns: predicted_for (date), source (badge: LLM / EWMA), direction (badge: rising/falling/flat with colour), confidence (progress bar 0–100), reasoning (truncated), generated_at
Filters: source, direction, date range
Default sort: predicted_for DESC
Actions:
- View (modal showing full reasoning text)
- Run prediction now (page-level action): executes
php artisan oil:predictviaArtisan::call(), shows success/failure notification
No edit/delete — predictions are immutable audit records.
3.4 Brent Prices (BrentPriceResource)
Purpose: Verify FRED fetch is populating data correctly.
Table columns: date, price_usd
Default sort: date DESC
Actions: none
Simple read-only table. No filters needed — data is always chronological. Include a line chart widget (BrentPriceChartWidget) showing the last 30 days using Filament's built-in chart widget.
3.5 Stations (StationResource)
Purpose: Browse the ~14,500 UK stations; verify supermarket tagging and closure flags.
Table columns: trading_name, brand_name, postcode, city, is_supermarket (badge), is_motorway_service_station (badge), temporary_closure (badge), last_seen_at
Filters: is_supermarket, is_motorway_service_station, temporary_closure, permanent_closure, postcode prefix (text search)
Search: trading_name, brand_name, postcode
Default sort: last_seen_at DESC
Actions:
- View (modal with full address, amenities, opening_times, fuel_types JSON)
- Trigger full poll (page-level action): dispatches a queued
PollFuelPricesJob(wrapsfuel:poll --full) rather than calling Artisan synchronously — full station refresh on 14,500 records would exceed HTTP timeout. Shows "Poll dispatched to queue" notification immediately; result visible in API Logs once complete.
No edit — station data is owned by Fuel Finder API, overwritten on each poll.
4. Resources Planned But Not Built Yet
These will be added once the underlying tables/services exist:
| Resource | Depends on |
|---|---|
| Alerts Log | alerts table + NotificationDispatchService |
| Scoring Results | scoring_results table + AlertScoringService |
| Subscriptions | Cashier integration + subscriptions table |
5. Navigation Groups
Dashboard (no group)
├── Users
Data
├── Stations
├── Brent Prices
├── Oil Predictions
System
├── API Logs
6. Build Order
- Migration — add
is_admintousers AdminSeeder— seeduovidiu@sent.comas adminAdminPanelProvider— mount panel, configure authApiLogResource— highest immediate valueUserResource— manage admin flagOilPredictionResource+ run-prediction actionBrentPriceResource+ chart widgetStationResource+ full-poll action- Dashboard widgets (last — depends on all resources being stable)
7. Out of Scope (v1)
- Role-based permissions beyond
is_admin— single admin user is sufficient - Activity log / audit trail of admin actions
- Dark mode customisation
- Custom Filament theme — use default
- Two-factor auth on admin panel — covered by app-level 2FA (Fortify)