diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..7c0d04a --- /dev/null +++ b/AGENT.md @@ -0,0 +1,2019 @@ +# 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` | `123123123` | +| User | `https://jabali.lan/jabali-panel` | `user@jabali.lan` | `wjqr9t6Z#%r&@C$4` | + +## 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 + +### 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 `