# Jabali Web Hosting Panel A modern web hosting control panel built with Laravel 12, Filament v5, and Livewire 4. ## Installation Requirements **Critical for mail server functionality:** - Fresh Debian 12/13 installation (no existing web/mail software) - Domain with glue records pointing to server IP (`ns1.domain.com` → IP) - PTR record (reverse DNS) pointing to mail hostname (`IP` → `mail.domain.com`) - Port 25 open (check with VPS provider) See README.md "Prerequisites" section for detailed DNS setup instructions. ### Subdomain Installation The panel can be installed on a subdomain (e.g., `panel.example.com`). The installer automatically: - Extracts the root domain (`example.com`) for DNS zone creation - Sets nameservers as `ns1.example.com` and `ns2.example.com` - Creates an A record for the subdomain (`panel` → server IP) - Configures email at the root domain (`webmaster@example.com`) **Example:** Installing on `panel.example.com` ``` DNS Zone: example.com NS Records: ns1.example.com, ns2.example.com A Records: @, www, panel, mail, ns1, ns2 → server IP Email: webmaster@example.com ``` ## Database The panel uses **SQLite** by default (not MySQL). The database file is located at: ``` /var/www/jabali/database/database.sqlite ``` ## Quick Reference ```bash # IMPORTANT: All artisan commands must be run from /var/www/jabali/ cd /var/www/jabali # Development composer dev # Start all dev servers (artisan, queue, pail, vite) composer test # Run PHPUnit tests ./vendor/bin/pint # Format PHP code php artisan serve # Web server only php artisan tinker # Interactive REPL # Production php artisan migrate # Run migrations php artisan config:cache # Cache configuration php artisan route:cache # Cache routes ``` ## Git Workflow **Important:** Only push to git when explicitly requested by the user. Do not auto-push after commits. ### Version Numbers **IMPORTANT:** Before every push, bump the `VERSION` in the `VERSION` file: ```bash # VERSION file format: VERSION=0.9-rc # Bump before pushing: # 0.9-rc → 0.9-rc1 → 0.9-rc2 → 0.9-rc3 → ... ``` | Field | When to Bump | Format | |-------|--------------|--------| | `VERSION` | Every push | `0.9-rc`, `0.9-rc1`, `0.9-rc2`, ... | ## Project Structure ``` /var/www/jabali/ ├── app/ │ ├── Filament/ │ │ ├── Admin/ # Admin panel (route: /admin) │ │ │ ├── Pages/ # Admin pages (Dashboard, Services, etc.) │ │ │ ├── Resources/# Admin resources (Users) │ │ │ └── Widgets/ # Admin widgets (Stats, Disk, Network) │ │ └── Jabali/ # User panel (route: /panel) │ │ ├── Pages/ # User pages (Domains, Email, WordPress, etc.) │ │ └── Widgets/ # User widgets (Stats, Disk, Domains) │ ├── Models/ # Eloquent models (22 models) │ ├── Services/ # Business logic services │ └── Console/Commands/ # Artisan commands ├── bin/ │ ├── jabali-agent # Privileged operations daemon (runs as root) │ └── screenshot # Chromium screenshot capture script ├── config/ # Laravel config files │ └── languages.php # Supported languages configuration ├── database/migrations/ # Database migrations ├── lang/ # Translation files (JSON) │ ├── en.json # English (base) │ ├── es.json # Spanish │ ├── fr.json # French │ ├── ru.json # Russian │ ├── pt.json # Portuguese │ ├── ar.json # Arabic (RTL) │ └── he.json # Hebrew (RTL) └── resources/views/filament/ # Blade templates for Filament pages ``` ## Architecture ### Two Panels - **Admin Panel** (`/admin`): Server-wide management, user administration - **User Panel** (`/panel`): Per-user domain, email, database management ### Privileged Agent The `bin/jabali-agent` daemon runs as root and handles operations requiring elevated privileges: - System user creation/deletion - Domain/vhost configuration - Email (Postfix/Dovecot) management - SSL certificate operations - Database operations - Backup operations Communication via Unix socket at `/var/run/jabali/agent.sock`. ### Key Services - **AgentClient**: PHP client for communicating with jabali-agent - **AdminNotificationService**: System notifications (SSL, backups, quotas) ### Self-Healing Services The `bin/jabali-health-monitor` daemon automatically monitors and restarts critical services when they fail. **Monitored Services:** - nginx, mariadb, jabali-agent, php-fpm - postfix, dovecot, named (if installed) - redis-server, fail2ban (if installed) **Features:** - Checks services every 30 seconds - Automatic restart on failure (up to 3 attempts) - Email notifications via `AdminNotificationService` - Systemd restart policies as backup protection **Files:** | File | Purpose | |------|---------| | `bin/jabali-health-monitor` | Health monitoring daemon | | `/etc/systemd/system/jabali-health-monitor.service` | Systemd service unit | | `/var/log/jabali/health-monitor.log` | Event log | | `/var/run/jabali/health-monitor.state` | Service state tracking | **Commands:** ```bash # Check status systemctl status jabali-health-monitor # View logs journalctl -u jabali-health-monitor -f # Manual notification test php artisan notify:service-health down nginx --description="Web Server" ``` **Notification Setting:** `notify_service_health` in dns_settings table (Admin > Server Settings) ### Admin Notifications & Monitoring The system sends email notifications to configured admin recipients for various events. **Configuration:** Admin > Server Settings > Notifications tab **Notification Types:** | Type | Setting | Description | |------|---------|-------------| | SSL Errors | `notify_ssl_errors` | Certificate errors and expiration warnings | | Backup Failures | `notify_backup_failures` | Failed scheduled backups | | Disk Quota | `notify_disk_quota` | Users reaching 90% quota | | Login Failures | `notify_login_failures` | Brute force and Fail2ban alerts | | SSH Logins | `notify_ssh_logins` | Successful SSH login alerts | | System Updates | `notify_system_updates` | Panel update availability | | Service Health | `notify_service_health` | Service failures and auto-restarts | | High Load | `notify_high_load` | Server load exceeds threshold | **Notification Log:** All sent notifications are logged in `notification_logs` table and viewable in Admin > Server Settings > Notifications tab. **High Load Monitoring:** - Monitors server load average every minute via `jabali-health-monitor` - Configurable threshold (default: 5.0) and duration (default: 5 minutes) - Sends alert when load exceeds threshold for configured duration - Settings: `load_threshold`, `load_alert_minutes` in dns_settings **Commands:** ```bash # Manual high load notification test php artisan notify:high-load # Test email # Use "Send Test Email" button in Server Settings > Notifications ``` ### Backup System The panel provides comprehensive backup functionality for both users and administrators. **Backup Types:** | Type | Description | Storage | |------|-------------|---------| | User Backup | Single user's domains, databases, mailboxes | `/home/{user}/backups/` | | Server Backup (Full) | All users as tar.gz archive | `/var/backups/jabali/` | | Server Backup (Incremental) | Rsync to remote destination | Remote SFTP/NFS | **Admin Backup Features:** - **Create Server Backup**: Backup all users or selected users - **Restore Backup**: Selective restore with modal UI - **Download Backup**: Download local backups (creates zip for directories) **Restore Options:** | Option | Description | |--------|-------------| | Website Files | Restore domain files to `/home/{user}/domains/` | | Databases | Restore MySQL databases with security sanitization | | MySQL Users | Restore MySQL users and their permissions | | Mailboxes | Restore email mailboxes and messages | | SSL Certificates | Restore SSL certificates for domains | | DNS Zones | Restore DNS zone files | **Backup Contents:** ``` backup_folder/ ├── manifest.json # Backup metadata ├── {username}.tar.gz # Per-user archive containing: │ ├── files/ # Domain files │ │ └── {domain}/ │ ├── mysql/ # Database dumps │ │ ├── {database}.sql.gz │ │ └── users.sql # MySQL users and grants │ ├── mail/ # Mailbox data │ │ └── {domain}/{user}/ │ ├── ssl/ # SSL certificates │ │ └── {domain}/ │ └── dns/ # DNS zone files │ └── {domain}.zone ``` **Security Validations (Restore):** The backup restore process includes comprehensive security checks to prevent privilege escalation: | Check | Description | |-------|-------------| | Database Prefix | Only restore databases matching user's prefix (`{username}_*`) | | MySQL Users | Only restore users with correct prefix, block global grants | | SQL Sanitization | Remove DEFINER, GRANT, SET GLOBAL from dumps | | Domain Ownership | Verify user owns domains before restoring files/SSL/DNS | | Symlink Prevention | Remove dangerous symlinks pointing outside backup | | Path Traversal | Block `..` sequences in paths | | DNS Validation | Validate zone files with `named-checkzone` | **Agent Actions:** ``` backup.create - Create user backup backup.restore - Restore backup with selective options backup.get_info - Get backup manifest/contents backup.create_server - Create server-wide backup backup.delete - Delete backup file backup.upload_remote - Upload backup to remote destination backup.download_remote - Download backup from remote backup.incremental - Create incremental backup via rsync backup.test_destination - Test remote backup destination ``` **Download Route:** ``` GET /jabali-admin/backup-download?id={backup_id} # Admin (requires is_admin) GET /jabali-panel/backup-download?path={base64} # User (validates ownership) ``` **Files:** | File | Purpose | |------|---------| | `app/Filament/Admin/Pages/Backups.php` | Admin backup management UI | | `app/Filament/Jabali/Pages/Backups.php` | User backup management UI | | `app/Http/Controllers/BackupDownloadController.php` | Download handlers | | `bin/jabali-agent` | Backup/restore implementation | | `/var/log/jabali/agent.log` | Security audit log for blocked operations | ### DNSSEC Support DNSSEC (Domain Name System Security Extensions) adds cryptographic signatures to DNS records. **Management:** Admin > Server Settings > DNS tab > DNSSEC section **Features:** - Enable/disable DNSSEC per domain - Automatic KSK (Key Signing Key) and ZSK (Zone Signing Key) generation - Uses ECDSAP256SHA256 algorithm - Auto-generates DS records for registrar configuration - Inline zone signing with auto-dnssec maintain **Agent Actions:** ``` dns.enable_dnssec - Generate keys and sign zone dns.disable_dnssec - Remove keys and unsign zone dns.get_dnssec_status - Check DNSSEC status and keys dns.get_ds_records - Get DS records for registrar ``` **Files:** | Path | Description | |------|-------------| | `/etc/bind/keys/{domain}/` | DNSSEC keys directory | | `/etc/bind/zones/db.{domain}` | Unsigned zone file | | `/etc/bind/zones/db.{domain}.signed` | Signed zone file | **Setup Process:** 1. Enable DNSSEC for domain in Server Settings 2. Copy DS record from modal 3. Add DS record to domain registrar 4. Wait for DNS propagation (up to 48 hours) ### Test Credentials | Panel | URL | Email | Password | |-------|-----|-------|----------| | Admin | `https://jabali.lan/jabali-admin` | `admin@jabali.lan` | `q1w2E#R$` | | User | `https://jabali.lan/jabali-panel` | `user@jabali.lan` | `wjqr9t6Z#%r&@C$4` | ### Demo Credentials | Panel | URL | Email | Password | |-------|-----|-------|----------| | Admin | `https://demo.jabali-panel.com/jabali-admin` | `admin@jabali-panel.com` | `demo1234` | | User | `https://demo.jabali-panel.com/jabali-panel` | `demo@jabali-panel.com` | `demo1234` | **Demo mode behavior** - `JABALI_DEMO=1` enables read-only mode via `App\Http\Middleware\DemoReadOnly`. - Livewire `authenticate` calls are allowed for unauthenticated users. - Some pages use static demo data to avoid agent socket calls. - Reverse proxy must set `X-Forwarded-Proto` and the app must trust proxies for HTTPS Livewire updates. ## Models | Model | Table | Description | |-------|-------|-------------| | User | users | Panel users (system users) | | Domain | domains | Hosted domains | | EmailDomain | email_domains | Email-enabled domains | | Mailbox | mailboxes | Email mailboxes | | EmailForwarder | email_forwarders | Email forwarding rules | | DnsRecord | dns_records | DNS zone records | | DnsSetting | dns_settings | Key-value settings store | | SslCertificate | ssl_certificates | SSL/TLS certificates | | MysqlCredential | mysql_credentials | Database credentials | | Backup | backups | User backups | | BackupSchedule | backup_schedules | Scheduled backups | | BackupDestination | backup_destinations | Remote backup targets | | CronJob | cron_jobs | User cron jobs | | AuditLog | audit_logs | Admin audit trail | | NotificationLog | notification_logs | Admin notification history | ## Filament Pages ### Admin Panel - Dashboard, Services, ServerStatus, ServerSettings - SslManager, PhpManager, EmailSettings, DnsZones - Backups, AuditLogs, Fail2ban, ClamAV, Security, ServerImports ### User Panel - Dashboard, Domains, DnsRecords, Files - Email, WordPress, Databases, Ssl - Backups, CronJobs, SshKeys, PhpSettings, Logs ## Dashboard Configurations ### Admin Dashboard (`/jabali-admin`) **Location:** `App\Filament\Admin\Pages\Dashboard` **Header Widgets:** - `DashboardStatsWidget` - Stats cards showing Users, Domains, Mailboxes, Databases, SSL Certificates - Uses `` components with icon, value (bold), label - Responsive: 1 col mobile, 2 cols tablet, 5 cols desktop **Schema Components:** - `RecentActivityTable` - Embedded audit log table via `EmbeddedTable::make()` **Header Actions:** - Refresh - Reloads the page - Setup Wizard - Onboarding modal (visible until completed) - Take Tour - Starts the admin panel tour **Files:** | File | Purpose | |------|---------| | `app/Filament/Admin/Pages/Dashboard.php` | Dashboard page class | | `app/Filament/Admin/Widgets/DashboardStatsWidget.php` | Stats widget | | `resources/views/filament/admin/widgets/dashboard-stats.blade.php` | Stats template | | `app/Filament/Admin/Widgets/Dashboard/RecentActivityTable.php` | Activity table widget | ### User Dashboard (`/jabali-panel`) **Location:** `App\Filament\Jabali\Pages\Dashboard` **Widgets (in order):** 1. `StatsOverview` - Stats cards showing Domains, Mailboxes, Databases, SSL Certificates - Responsive: 1 col mobile, 2 cols tablet, 4 cols desktop 2. `DiskUsageWidget` - Disk quota visualization with progress bar 3. `DomainsWidget` - Recent domains table with SSL status 4. `MailboxesWidget` - Recent mailboxes table 5. `RecentBackupsWidget` - Latest backups table **Layout:** 2-column responsive grid (1 col mobile, 2 cols desktop) **Subheading:** Personalized welcome message ("Welcome back, {name}!") **Files:** | File | Purpose | |------|---------| | `app/Filament/Jabali/Pages/Dashboard.php` | Dashboard page class | | `app/Filament/Jabali/Widgets/StatsOverview.php` | Stats widget | | `resources/views/filament/jabali/widgets/stats-overview.blade.php` | Stats template | | `app/Filament/Jabali/Widgets/DiskUsageWidget.php` | Disk usage widget | | `app/Filament/Jabali/Widgets/DomainsWidget.php` | Domains table widget | | `app/Filament/Jabali/Widgets/MailboxesWidget.php` | Mailboxes table widget | | `app/Filament/Jabali/Widgets/RecentBackupsWidget.php` | Backups table widget | ## Agent Actions The jabali-agent supports these action categories: ``` user.* - System user management domain.* - Domain/vhost operations wp.* - WordPress installation/management email.* - Email domain/mailbox operations mysql.* - Database operations dns.* - DNS zone management (includes DNSSEC) php.* - PHP version management ssl.* - SSL certificate operations backup.* - Backup/restore operations (see Backup System section) service.* - System service control ufw.* - Firewall management file.* - File operations ssh.* - SSH key management cron.* - Cron job management quota.* - Disk quota management clamav.* - Antivirus scanning metrics.* - System metrics scanner.* - Security scanning (Lynis, Nikto) logs.* - Log access redis.* - Redis user management ``` **Detailed Action Reference:** | Action | Parameters | Description | |--------|------------|-------------| | `dns.enable_dnssec` | `domain` | Generate keys and sign zone | | `dns.disable_dnssec` | `domain` | Remove keys and unsign zone | | `dns.get_dnssec_status` | `domain` | Check DNSSEC status and keys | | `dns.get_ds_records` | `domain` | Get DS records for registrar | | `backup.create` | `username`, `options` | Create user backup | | `backup.restore` | `username`, `backup_path`, `restore_*` | Restore with selective options | | `backup.get_info` | `backup_path` | Get backup manifest | | `backup.create_server` | `path`, `options` | Create server backup | | `backup.delete` | `path` | Delete backup | | `backup.upload_remote` | `local_path`, `config` | Upload to remote | | `backup.download_remote` | `remote_path`, `local_path`, `config` | Download from remote | | `backup.incremental` | `config`, `options` | Incremental rsync backup | | `backup.test_destination` | `config` | Test remote destination | | `backup.delete_remote` | `remote_path`, `config` | Delete backup from remote | ## Backup System The backup system supports both user-level and server-wide backups with local and remote storage. ### Backup Types | Type | Description | Storage | |------|-------------|---------| | **Full (tar.gz)** | Complete archive of all data | Local or remote | | **Incremental (rsync)** | Space-efficient with hard links | Remote only (SFTP/NFS) | ### Remote Destinations Supported destination types: - **SFTP** - SSH-based file transfer to remote servers - **NFS** - Network File System mounts - **S3** - S3-compatible object storage (AWS, MinIO, etc.) ### Backup Schedules & Retention Scheduled backups run via the `backups:run-schedules` artisan command (called by cron). Each schedule has: - **Frequency**: Hourly, daily, weekly, or monthly - **Retention Count**: Number of backups to keep (default: 7) - **Destination**: Local or remote storage **Retention Policy:** - Applied automatically after each backup completes - Deletes oldest backups beyond the retention count - Works for both local files and remote destinations - Implemented in `App\Jobs\RunServerBackup::applyRetention()` **Important:** The queue worker must be running for scheduled backups and retention: ```bash systemctl status jabali-queue # Check status systemctl restart jabali-queue # Restart to pick up code changes ``` ### Backup Files | Path | Description | |------|-------------| | `/var/backups/jabali/` | Default server backup location | | `/home/{user}/backups/` | User backup location | | `metadata/panel_data.json` | Panel database records (domains, mailboxes, etc.) | | `mysql/` | Database dumps (.sql.gz) | | `zones/` | DNS zone files | | `ssl/` | SSL certificates | | `mail/` | Mailbox data | ### Related Models - `Backup` - Individual backup records - `BackupSchedule` - Schedule configuration with retention settings - `BackupDestination` - Remote storage configuration ### Related Jobs - `RunServerBackup` - Executes backup and applies retention - `IndexRemoteBackups` - Indexes backups on remote destinations ## Security Features ### Security Page (Admin) Located at `/jabali-admin/security`, provides: - **Overview** - System security status and recent audit logs - **Firewall** - UFW rules management - **Fail2ban** - Intrusion prevention with jail management - **Antivirus** - ClamAV scanning - **SSH** - SSH hardening settings - **Vulnerability Scanner** - Security scanning tools ### Security Scanning Tools | Tool | Purpose | Installation | |------|---------|--------------| | **Lynis** | System security auditing | Pre-installed | | **Nikto** | Web server vulnerability scanner | `/opt/nikto` (GitHub clone) | | **WPScan** | WordPress vulnerability scanner | Ruby gem | **Nikto Configuration:** - Installed from GitHub at `/opt/nikto` - Symlinked to `/usr/local/bin/nikto` - Called with full path in timeout commands (PATH restriction) **WPScan Configuration:** - Cache directory: `/var/www/.wpscan` (owned by www-data) - Run with `HOME=/var/www` environment variable - Version displayed without ASCII banner using `grep -i 'version'` ### Audit Logs All administrative actions are logged to the `audit_logs` table. **Logged Events:** - Authentication (login, logout, login_failed) - CRUD operations on domains, mailboxes, users - System configuration changes - Security-related actions **Audit Log Retention:** - Configured via `audit_log_retention_days` setting (default: 90 days) - Pruned daily at 2:00 AM via scheduled task - Method: `AuditLog::prune()` **Viewing Logs:** - Admin Panel: Security page → Overview tab (paginated table) - Direct page: `/jabali-admin/audit-logs` (hidden from sidebar) ### Related Files | File | Description | |------|-------------| | `app/Models/AuditLog.php` | Audit log model with prune method | | `app/Filament/Admin/Pages/Security.php` | Security dashboard | | `app/Filament/Admin/Widgets/Security/AuditLogsTable.php` | Audit logs table widget | | `routes/console.php` | Scheduled audit log pruning | ## Code Style - PHP 8.2+ with strict types - Follow Laravel conventions - Use `DnsSetting::get()` / `DnsSetting::set()` for key-value storage - All privileged operations go through jabali-agent ## Filament v4 UI Guidelines **CRITICAL: Build using ONLY Filament v4 native components and styles with proper spacing. Always use pure Filament native components with proper icons and colors.** ### Architecture Guidelines 1. **Use Filament Resources for Eloquent Models**: When data is backed by an Eloquent model, prefer creating a Filament Resource (`php artisan make:filament-resource`) over custom pages. 2. **Use Tables for List Data**: When displaying list data (arrays or collections), ALWAYS use proper Filament tables via `EmbeddedTable::make()` or the `HasTable` trait. 3. **Use Schema-based Pages for Complex UIs**: For pages with multiple sections, tabs, and mixed content (forms + tables + stats), use Schema-based pages with embedded table widgets. 4. **Prefer Existing Patterns**: Look at existing pages in the codebase (e.g., `Security.php`, `DnsRecords.php`, `SshKeys.php`) for examples of how to implement similar features. ### Absolute Rules (NO EXCEPTIONS) - **NO custom HTML** - Never use raw `
`, ``, `

`, `

    ` etc. in Filament pages - **NO inline styles** - Never use `style="..."` attributes - **NO custom CSS** - Never create CSS files for Filament components - **NO custom blade templates** - Don't create View components with custom HTML for UI elements - **USE Filament components** - Sections, badges, buttons, tables, infolists, stats widgets, grids - **USE proper icons** - Always use `->icon('heroicon-o-*')` on sections and actions - **USE proper colors** - Always use `->iconColor('success'|'danger'|'warning'|'gray')` for status indication - **USE Tailwind classes** - Only when absolutely necessary for minor adjustments - **MUST be responsive** - All pages must work on mobile, tablet, and desktop ### Warning Banners - Use Filament `Section::make()` for warning banners (no raw HTML). - Always set `->icon('heroicon-o-exclamation-triangle')` and `->iconColor('warning')`. - Keep banners non-collapsible: `->collapsed(false)->collapsible(false)`. - Put the full message in `->description()` and keep the heading short. ### Allowed Components Use these Filament native components exclusively: | Category | Components | |----------|------------| | Layout | `Section::make()`, `Grid::make()`, `Group::make()`, `Tabs::make()` | | Display | `Text::make()`, ``, `` | | Actions | `Actions::make()`, `Action::make()`, `` | | Forms | TextInput, Select, Toggle, FileUpload, Checkbox, etc. | | Data | Tables, Infolists, Stats Widgets, `EmbeddedTable::make()` | | Feedback | ``, Notifications | ### Tables with Array Data (IMPORTANT) When displaying list data, **ALWAYS use proper Filament tables**, not Sections or custom HTML. Use `EmbeddedTable::make()` to embed table widgets within Schema-based pages. **Creating a Self-Refreshing Table Widget:** For tables with actions that modify data (enable/disable, delete, etc.), the widget should reload its own data directly rather than dispatching events to the parent (which causes full page re-renders). ```php send('some.action', []); if ($result['success'] ?? false) { $this->items = $result['items'] ?? []; $this->resetTable(); // Force table to re-render with new data } } catch (\Exception $e) { // Keep existing data on error } } public function makeFilamentTranslatableContentDriver(): ?\Filament\Support\Contracts\TranslatableContentDriver { return null; } public function table(Table $table): Table { return $table ->records(fn () => $this->items) ->columns([ TextColumn::make('name')->label(__('Name'))->searchable(), IconColumn::make('enabled') ->label(__('Status')) ->boolean() ->trueIcon('heroicon-o-check-circle') ->falseIcon('heroicon-o-x-circle') ->trueColor('success') ->falseColor('gray'), ]) ->actions([ Action::make('toggle') ->label(fn (array $record): string => ($record['enabled'] ?? false) ? __('Disable') : __('Enable')) ->icon(fn (array $record): string => ($record['enabled'] ?? false) ? 'heroicon-o-x-circle' : 'heroicon-o-check-circle') ->color(fn (array $record): string => ($record['enabled'] ?? false) ? 'danger' : 'success') ->action(function (array $record): void { try { $agent = new AgentClient(); $result = $agent->send('item.toggle', ['id' => $record['id']]); if ($result['success'] ?? false) { Notification::make() ->title(__('Status updated')) ->success() ->send(); // Reload data directly - don't dispatch events to parent $this->reloadData(); } else { throw new \Exception($result['error'] ?? __('Operation failed')); } } catch (\Exception $e) { Notification::make() ->title(__('Error')) ->body($e->getMessage()) ->danger() ->send(); } }), ]) ->striped() ->emptyStateHeading(__('No items')) ->emptyStateIcon('heroicon-o-inbox'); } public function render() { return $this->getTable()->render(); } } ``` **Embedding Table in Schema:** ```php use Filament\Schemas\Components\EmbeddedTable; use App\Filament\Admin\Widgets\Security\MyDataTable; // In your page's schema Section::make(__('Data List')) ->icon('heroicon-o-list-bullet') ->schema([ EmbeddedTable::make(MyDataTable::class, ['items' => $this->items]), ]) ``` **Key Points:** - Implement `HasTable`, `HasSchemas`, and `HasActions` interfaces - Use `InteractsWithTable`, `InteractsWithSchemas`, and `InteractsWithActions` traits - Use `->records(fn () => $this->arrayData)` for array/collection data - Use `->query(Model::query())` for Eloquent models - Use `->actions([])` for row actions on array-based tables - Always implement `makeFilamentTranslatableContentDriver()` returning null - Return `$this->getTable()->render()` in render method (NOT `view('filament-tables::index')`) - Import actions from `Filament\Actions\Action` (NOT `Filament\Tables\Actions\Action`) - **After modifying data, call `$this->resetTable()` to force the table to re-render** - **Reload data directly in the widget rather than dispatching events to parent** (avoids full page re-renders) ### Section with Icons and Colors Always use Section's native icon and color support: ```php use Filament\Schemas\Components\Section; // Status card with icon and color Section::make(__('Active')) ->description(__('Firewall')) ->icon('heroicon-o-shield-check') ->iconColor('success') // success, danger, warning, gray // Section with header actions Section::make(__('Settings')) ->icon('heroicon-o-cog') ->headerActions([ Action::make('save')->label(__('Save')), ]) ->schema([...]) ``` ### Responsive Grid Layout Use Grid with responsive column configuration: ```php use Filament\Schemas\Components\Grid; use Filament\Schemas\Components\Section; // 3-column responsive grid for stat cards Grid::make(['default' => 1, 'sm' => 3]) ->schema([ Section::make(__('Active')) ->description(__('Firewall')) ->icon('heroicon-o-shield-check') ->iconColor('success'), Section::make('0') ->description(__('IPs Banned')) ->icon('heroicon-o-lock-closed') ->iconColor('success'), Section::make('0') ->description(__('Threats')) ->icon('heroicon-o-bug-ant') ->iconColor('gray'), ]) ``` ### Stats Overview Widget Pattern For dashboard stats (domains count, mailboxes count, etc.), use a custom Widget with `` components in a blade template. **Widget class:** ```php $userCount, 'label' => __('Users'), 'icon' => 'heroicon-o-users', 'color' => 'primary', ], [ 'value' => $domainCount, 'label' => __('Domains'), 'icon' => 'heroicon-o-globe-alt', 'color' => 'success', ], // ... more stats ]; } } ``` **Blade template** (`resources/views/filament/{panel}/widgets/dashboard-stats.blade.php`): ```blade
    @foreach($this->getStats() as $stat) {{ $stat['value'] }} {{ $stat['label'] }} @endforeach
    ``` **Key points:** - Use custom Widget extending `Filament\Widgets\Widget` with a blade view - Use `` for each stat card with icon and icon-color - Value goes in `heading` slot with bold styling, label goes in `description` slot - Use CSS media queries in `