# A-Monster Guard — v1.0 Critical Fixes & Workflow Improvements (Revised)

Production blockers and UX improvements across 9 workstreams. No UI redesign. No unrelated refactoring.

---

## Execution Order

| Phase | Workstream | Risk | Est. Files |
|-------|-----------|------|------------|
| 1 | Remove Client Management | Low | ~12 |
| 2 | Database migrations (all new schema) | Low | 4 migrations |
| 3 | Telegram Notifications pipeline | Medium | ~8 |
| 4 | Disconnect Protection (hashed secrets) | High | ~8 |
| 5 | Security Hardening + TOTP 2FA | High | ~15 |
| 6 | Configurable Heartbeat Interval | Low | ~3 |
| 7 | Last Check-in + Pending Actions | Low | ~4 |
| 8 | Infrastructure Health (live checks) | Medium | ~3 |
| 9 | Testing & Verification | — | ~6 |

---

## 1. Remove Client Management (Complete Removal)

Pre-production project, single administrator, no multi-tenancy.

---

#### [DELETE] [Client.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Models/Client.php)

#### [DELETE] [ClientAdminController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Controllers/ClientAdminController.php)

#### [DELETE] [ClientsView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/agents/components/ClientsView.tsx)

#### [NEW] `database/migrations/2026_06_30_000001_remove_clients_system.php`

- Add pre-migration verification: query `clients` and check if any non-seeded or production data exists. If production data exists, abort the migration and display a manual confirmation prompt/error. If in local or pre-production environment with only default seeded data, perform the migration automatically.
- Drop foreign key `agents.client_id` → `clients.id`
- Drop column `agents.client_id`
- Drop table `clients`

#### [MODIFY] [Agent.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Models/Agent.php)

- Remove `client_id` from `#[Fillable]`
- Remove `client()` relationship method
- Remove `use App\Features\Agent\Models\Client` import

#### [MODIFY] [api.php](file:///c:/laragon/www/a-monster-panel/routes/api.php)

- Remove `Route::apiResource('/clients', ClientAdminController::class)`
- Remove `use App\Features\Agent\Controllers\ClientAdminController`

#### [MODIFY] [AgentAdminController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Controllers/AgentAdminController.php)

- Remove `'client'` from all `with()` eager loads
- Remove `client_id` from search query (`orWhereHas('client', ...)`)
- Remove `client_id` filter in query builder
- Remove client sort join (`leftJoin('clients', ...)`)
- Remove `client_id` from `update()` validation rules
- Remove `'client'` from `load()` calls in responses

#### [MODIFY] [AgentRegistrationService.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Services/AgentRegistrationService.php)

- Remove `$clientId = Client::where(...)` lookup
- Remove `client_id` from `create()` and `update()` calls
- Remove `use App\Features\Agent\Models\Client`

#### [MODIFY] [app.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/app.tsx)

- Remove `import { ClientsView }` 
- Remove `{currentTab === 'clients' && <ClientsView />}`

#### [MODIFY] [DashboardLayout.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/layouts/DashboardLayout.tsx)

- Remove `{ id: 'clients', name: 'Client Accounts', icon: Users }` from `navSections`
- Remove `Users` from lucide imports

#### [MODIFY] [AgentsView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/agents/components/AgentsView.tsx)

- Remove `clients` state, `setClients`, and `axios.get('/clients')` fetch
- Remove "Filter by Client" `<select>` dropdown
- Remove "Client" column from `<thead>` and `<tbody>`
- Remove `client_id` from all `axios.put('/agents/...')` payloads
- Remove `client?.name` / `'Internal SOC Client'` references in detail views
- Update description: "Manage client websites" → "Manage websites and coordinate security policies"

#### [MODIFY] [DatabaseSeeder.php](file:///c:/laragon/www/a-monster-panel/database/seeders/DatabaseSeeder.php)

- Remove `client_id` from Agent `create()` calls (field will no longer exist)

---

## 2. Telegram Notifications (Database-Driven, No .env)

Telegram config is **database-driven only**. The Default Telegram Profile is the global notification destination. System MUST NOT depend on `.env` for Bot Token or Chat ID.

**Resolution priority:**
1. Website-specific Policy override (`telegram_bot_token` in agent policy settings)
2. Assigned Telegram Profile (`agent.telegram_profile_id`)
3. Default Telegram Profile (`telegram_profiles.is_default = true`)

**If no profile exists:** Log failure explicitly, do NOT silently fail, show "Not Configured" in UI.

---

#### [MODIFY] [SendTelegramNotification.php](file:///c:/laragon/www/a-monster-panel/app/Features/Notifications/Jobs/SendTelegramNotification.php)

- Set `$tries = 3` and `$backoff = [10, 30, 60]` for automatic retry
- **Remove** the `.env` fallback (`env('TELEGRAM_BOT_TOKEN')`) entirely
- Simplify credential resolution to 3 levels:
  1. Website Policy override
  2. Assigned Telegram Profile
  3. Default Telegram Profile
- If no credentials found: `Log::error()` with explicit "Telegram not configured" message (not warning — this is a real failure)
- After successful send: update `last_notification_at` on the resolved `TelegramProfile`
- Log failed HTTP requests with: status code, response body, agent name, event type
- Add message templates for ALL events:
  - `malware_detected`, `agent_offline`, `deactivation_blocked`
  - `emergency_lock`, `login_alert`, `plugin_whitelist_block`
  - `xmlrpc_block`, `disconnect_authorized`, `disconnect_unauthorized`
  - `secret_rotation`, `policy_update`

#### [MODIFY] [PolicyEngineService.php](file:///c:/laragon/www/a-monster-panel/app/Features/Policy/Services/PolicyEngineService.php)

- Remove `env('TELEGRAM_BOT_TOKEN', '')` and `env('TELEGRAM_CHAT_ID', '')` from `getDefaultGlobalSettings()`
- Default to empty strings — the Default Telegram Profile is the source of truth

#### [NEW] `database/migrations/2026_06_30_000002_add_telegram_tracking_fields.php`

- Add to `telegram_profiles`:
  - `last_notification_at` (timestamp, nullable)
  - `connection_status` (string, default `'unknown'`) — values: `verified`, `failed`, `unknown`
  - `verified_at` (timestamp, nullable)

#### [MODIFY] [TelegramProfile.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Models/TelegramProfile.php)

- Add `last_notification_at`, `connection_status`, `verified_at` to `$fillable`
- Add datetime casts for `last_notification_at`, `verified_at`

#### [MODIFY] [TelegramProfileController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Controllers/TelegramProfileController.php)

- Add `sendTest(int $id)` — sends test message, updates `connection_status` + `last_notification_at`
- Add `verify(int $id)` — calls Telegram `getMe` API, updates `connection_status` + `verified_at`

#### [MODIFY] [api.php](file:///c:/laragon/www/a-monster-panel/routes/api.php)

- Add `POST /telegram-profiles/{id}/test`
- Add `POST /telegram-profiles/{id}/verify`

#### [MODIFY] [TelegramView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/telegram/components/TelegramView.tsx)

- Add "Send Test Notification" button per profile (with loading state)
- Add "Verify Bot" button per profile
- Show connection status badge: `✓ Verified` / `⚠ Unverified` / `✗ Failed` / `Not Configured`
- Show "Last Notification Sent" timestamp per profile
- If no profiles exist: show prominent "Not Configured" warning

#### [MODIFY] [TelemetryProcessingService.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Services/TelemetryProcessingService.php)

- Dispatch notifications for events currently missing:
  - `login.success`, `login.failed` → `login_alert`
  - `plugin_whitelist.blocked` → `plugin_whitelist_block`
  - `xmlrpc.blocked` → `xmlrpc_block`

#### [MODIFY] [AgentAdminController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Controllers/AgentAdminController.php)

- Dispatch `SendTelegramNotification` for:
  - `emergency_lock` bulk action
  - `rotate_secret` bulk action → `secret_rotation` event
  - `sync_policy` bulk action → `policy_update` event

#### [MODIFY] [MonitoringAdminController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Monitoring/Controllers/MonitoringAdminController.php)

- `getHealthStatus()` → update Telegram health check to show `"Not Configured"` when no default profile exists (instead of silently showing healthy=false)

---

## 3. Disconnect Protection (Hashed Secrets, One-Time Only)

Disconnect MUST require Control Center authorization. No local bypass. No database bypass. No accidental disconnect.

**Security requirements:**
- Disconnect secrets generated as `bin2hex(random_bytes(16))`
- Stored ONLY as `Hash::make($secret)` — NEVER plain text
- Verified with `Hash::check($enteredSecret, $storedHash)`
- After successful disconnect: remove hash, remove `disconnect_allowed`, expire immediately
- Every authorization is **one-time only**
- Log every attempt
- Telegram notification for: authorized disconnect, unauthorized attempt, expired secret, invalid secret

**Two authorization paths:**

**Path A — One-Time Secret:**
Control Center → Generate secret → Admin enters in WP plugin → Verify → Disconnect

**Path B — Policy Flag:**
Control Center → Enable "Allow Disconnect" → Next heartbeat → Plugin stores flag → Disconnect allowed once

---

#### [NEW] `database/migrations/2026_06_30_000003_add_disconnect_fields_to_agents.php`

- Add `disconnect_secret_hash` (string, nullable) — bcrypt hash only
- Add `disconnect_secret_expires_at` (timestamp, nullable)
- Add `disconnect_allowed_at` (timestamp, nullable) — one-time flag

#### [MODIFY] [Agent.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Models/Agent.php)

- Add `disconnect_secret_hash`, `disconnect_secret_expires_at`, `disconnect_allowed_at` to `#[Fillable]`
- Add datetime casts for the timestamp fields

#### [MODIFY] [AgentAdminController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Controllers/AgentAdminController.php)

- Add `allowDisconnect(string $id)`:
  - Generate `$secret = bin2hex(random_bytes(16))`
  - Store `Hash::make($secret)` in `disconnect_secret_hash`
  - Set `disconnect_secret_expires_at` to `now()->addHours(1)` (1-hour expiry)
  - Return the plain secret to admin (one-time display only)
  - Log to audit: "Disconnect secret generated for {site_name}"
  - Dispatch Telegram notification: `disconnect_request`

- Add `enableDisconnect(string $id)`:
  - Set `disconnect_allowed_at = now()`
  - On next heartbeat, agent receives `disconnect_allowed = true` in policy
  - Log to audit: "Disconnect enabled via policy for {site_name}"

#### [MODIFY] [api.php](file:///c:/laragon/www/a-monster-panel/routes/api.php)

- Add `POST /agents/{id}/allow-disconnect`
- Add `POST /agents/{id}/enable-disconnect`

#### [MODIFY] [AgentTelemetryController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Controllers/AgentTelemetryController.php) or [TelemetryProcessingService.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Services/TelemetryProcessingService.php)

- Include `disconnect_allowed` flag in heartbeat response when `disconnect_allowed_at` is set
- After delivering the flag, clear `disconnect_allowed_at` (consumed, one-time)

#### WordPress Agent Changes:

#### [MODIFY] [ConnectionService.php](file:///c:/laragon/www/a-monster-panel/A-Monster%20Guard%20WordPress%20Agent/src/Connection/ConnectionService.php)

- `disconnect()` now requires authorization:
  - Accept `?string $secret = null` parameter
  - Check 1: If `disconnect_allowed` flag is stored locally → allow, consume flag
  - Check 2: If `$secret` provided → send to Control Center for hash verification via API call
  - If neither: throw `AgentException('Disconnect requires authorization from Control Center')`

#### [MODIFY] [ConnectionPage.php](file:///c:/laragon/www/a-monster-panel/A-Monster%20Guard%20WordPress%20Agent/src/UI/ConnectionPage.php)

- `handleDisconnect()` → require `disconnect_secret` form input
- Render disconnect form with secret input field (same UX pattern as deactivation token)
- Show message: "Generate a disconnect secret from the Control Center, or enable Allow Disconnect."

#### [MODIFY] [HeartbeatService.php](file:///c:/laragon/www/a-monster-panel/A-Monster%20Guard%20WordPress%20Agent/src/Heartbeat/HeartbeatService.php)

- Read `disconnect_allowed` from policy response
- Store locally (wp_option) — consumed on first disconnect attempt

#### Telegram Notifications for Disconnect:

| Event | Notification |
|-------|-------------|
| Authorized disconnect (valid secret) | ✅ `disconnect_authorized` |
| Authorized disconnect (policy flag) | ✅ `disconnect_authorized` |
| Unauthorized attempt (no auth) | 🚨 `disconnect_unauthorized` |
| Invalid secret entered | 🚨 `disconnect_unauthorized` (reason: invalid secret) |
| Expired secret used | 🚨 `disconnect_unauthorized` (reason: expired) |

---

## 4. Configurable Heartbeat Interval

---

#### [MODIFY] [PolicyView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/policy/components/PolicyView.tsx)

- Replace the free-text `<input type="number">` for heartbeat interval with a dropdown `<select>`:

| Value (seconds) | Label |
|----------------|-------|
| 60 | 1 minute |
| 120 | 2 minutes |
| 180 | 3 minutes |
| 300 | 5 minutes (default) |
| 600 | 10 minutes |
| 900 | 15 minutes |
| 1800 | 30 minutes |
| 3600 | 60 minutes |

- When **1 minute** is selected, show warning banner:
  > ⚠️ "This interval may significantly increase server load when managing many websites."

#### [MODIFY] [AgentsView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/agents/components/AgentsView.tsx)

- In website detail → Protection Rules tab: add heartbeat interval override dropdown (same options)
- Show same 1-minute warning

**Note:** No WP Agent changes needed — `HeartbeatScheduler` already dynamically reads interval from synced policy and reschedules WP-Cron automatically. No manual reconnect required.

---

## 5. Security Hardening + TOTP 2FA + Trusted Devices

---

### 5a. Session Security (Environment-Aware)

#### [MODIFY] `.env`

- Add (but do NOT hardcode `true`):
```
SESSION_SECURE_COOKIE=false
SESSION_ENCRYPT=true
SESSION_SAME_SITE=lax
```

#### [MODIFY] `.env.example`

- Add with production values:
```
SESSION_SECURE_COOKIE=true
SESSION_ENCRYPT=true
SESSION_SAME_SITE=strict
```

**Behavior:** Dev uses `false`, production uses `true`. HTTPS deployments automatically use secure cookies via the env var.

---

### 5b. Security Headers Middleware

#### [NEW] `app/Http/Middleware/SecurityHeaders.php`

- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `X-XSS-Protection: 1; mode=block` (Included ONLY as a legacy compatibility header; do not rely on it for security)
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`
- `Content-Security-Policy` (Prioritize a strong CSP, restricting sources for script, style, img, etc., appropriate for the React SPA app)
- `Strict-Transport-Security: max-age=31536000; includeSubDomains` (conditional on HTTPS)

#### [MODIFY] [api.php](file:///c:/laragon/www/a-monster-panel/routes/api.php)

- Register `SecurityHeaders` middleware on all routes

---

### 5c. Login Security

#### [MODIFY] [AuthController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Auth/Controllers/AuthController.php)

- Add rate limiting: `throttle:5,1` on login route
- Log all login attempts (success + failure) to audit log via `AuditLog::create()`
- Log IP address, user agent, timestamp
- Fix `Auth::attempt($credentials, true)` → make `remember` optional based on form input
- After login: check for TOTP enrollment → if enabled, require 2FA verification before granting session

#### [MODIFY] [VerifyAgentSignature.php](file:///c:/laragon/www/a-monster-panel/app/Features/Agent/Middleware/VerifyAgentSignature.php)

- Add `Log::warning()` for failed authentication attempts with: agent ID, IP, failure reason

---

### 5d. TOTP Two-Factor Authentication

Two-Factor Authentication (TOTP) is optional and can be enabled or disabled by the administrator from Security Settings. The first login after installation must not require 2FA until the administrator explicitly enables it.

#### [NEW] `database/migrations/2026_06_30_000004_add_2fa_fields_to_users.php`

- Add to `users` table:
  - `two_factor_secret` (text, nullable, encrypted) — TOTP secret key
  - `two_factor_enabled` (boolean, default false) — Enforces optional behavior; defaults to false so the first login does not require 2FA.
  - `two_factor_recovery_codes` (text, nullable, encrypted) — JSON array of backup codes
  - `two_factor_confirmed_at` (timestamp, nullable)

#### [MODIFY] [User.php](file:///c:/laragon/www/a-monster-panel/app/Features/Auth/Models/User.php)

- Add 2FA fields to fillable
- Add encrypted casts for `two_factor_secret` and `two_factor_recovery_codes`
- Add helper methods: `hasTwoFactorEnabled()`, `validateTotpCode()`, `useRecoveryCode()`

#### [NEW] `app/Features/Auth/Controllers/TwoFactorController.php`

- `enable()` — Generate TOTP secret, return QR code provisioning URI + backup recovery codes
- `confirm(Request $request)` — Verify initial TOTP code to confirm enrollment
- `disable(Request $request)` — Require password + TOTP code to disable
- `verifyChallenge(Request $request)` — Called during login flow when 2FA is required
- `regenerateRecoveryCodes()` — Generate new backup codes (invalidates old)

#### [NEW] `app/Features/Auth/Controllers/TrustedDeviceController.php`

- `index()` — List active sessions from `sessions` table (IP, user_agent, last_activity)
- `revoke(string $sessionId)` — Terminate a specific session
- `revokeAll()` — Terminate all sessions except current

#### [MODIFY] [api.php](file:///c:/laragon/www/a-monster-panel/routes/api.php)

- Add 2FA routes (authenticated):
  - `POST /2fa/enable`
  - `POST /2fa/confirm`
  - `POST /2fa/disable`
  - `POST /2fa/challenge`
  - `POST /2fa/recovery-codes`
- Add trusted device routes:
  - `GET /sessions`
  - `DELETE /sessions/{id}`
  - `DELETE /sessions` (revoke all)

#### [MODIFY] [AuthController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Auth/Controllers/AuthController.php)

- After successful credential check: if `two_factor_enabled`, return `{ requires_2fa: true }` instead of session
- Login flow becomes: credentials → 2FA challenge → session created
- New device login → dispatch `SendTelegramNotification` with `login_alert` event

#### [MODIFY] [useAuthStore.ts](file:///c:/laragon/www/a-monster-panel/resources/js/features/auth/store/useAuthStore.ts)

- Handle `requires_2fa` response from login
- Add `verify2FA` action
- Track `requires2FA` state

#### [MODIFY] [LoginForm.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/auth/components/LoginForm.tsx)

- Add 2FA code input step (shows after credentials accepted when 2FA enabled)
- Add recovery code fallback option

#### [NEW] `resources/js/features/auth/components/SecuritySettingsView.tsx`

- 2FA enrollment/disable UI with QR code display
- Recovery codes display + regenerate
- Active sessions list (trusted devices) with revoke buttons
- Current session highlighted

#### [MODIFY] [DashboardLayout.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/layouts/DashboardLayout.tsx)

- Add "Security Settings" nav item under System section

#### [MODIFY] [app.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/app.tsx)

- Add `SecuritySettingsView` route

#### PHP Dependency

- Will use `pragmarx/google2fa-laravel` or implement TOTP manually using `HMAC-SHA1` (PHP has native support via `hash_hmac`)
- QR code generation via inline SVG or `chillerlan/php-qrcode`

> [!IMPORTANT]
> Need to install: `composer require pragmarx/google2fa-laravel` and `composer require bacon/bacon-qr-code` (for QR generation). These are standard, well-maintained packages.

---

## 6. Last Check-in (Dynamic Real Values)

---

#### [MODIFY] [DashboardLayout.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/layouts/DashboardLayout.tsx)

- Replace hardcoded `"Last Check-in 5m"` with dynamic value
- Fetch most recent `last_heartbeat_at` across all agents via lightweight API call
- Auto-update every 15 seconds via `setInterval`

#### Shared `timeAgo()` improvements (applied in Dashboard, Agents, Pending Actions):

| Elapsed | Display |
|---------|---------|
| < 10s | "Just now" |
| < 60s | "35 seconds ago" |
| < 3600s | "2 minutes ago" |
| < 86400s | "1 hour ago" / "8 hours ago" |
| 1-2 days | "Yesterday" |
| > 2 days | "3 days ago" |

#### [MODIFY] [DashboardView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/dashboard/components/DashboardView.tsx)

- Update `timeAgo()` function with human-friendly labels above
- Add auto-refresh interval (every 15 seconds) for timestamp updates

#### [MODIFY] [AgentsView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/agents/components/AgentsView.tsx)

- Apply same `formatTime()` improvement
- Add auto-refresh for timestamp fields (every 15 seconds)

---

## 7. Pending Actions Dashboard Widget

Show **Queued**, **Running**, and **Recent Failed** only. Completed actions visible in Website Details → Actions History.

---

#### [MODIFY] [MonitoringAdminController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Monitoring/Controllers/MonitoringAdminController.php)

- `getPendingCommands()` → fetch commands with status `pending`, `sent` (running), and `failed`
- Exclude `completed` from dashboard widget (those go to Website Details → Actions History)
- Eager load `agent:id,site_name,domain`
- Include `created_at` timestamp
- Limit 30

#### [MODIFY] [DashboardView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/dashboard/components/DashboardView.tsx)

- Redesign "Pending Actions" panel rows to show:

| Column | Source |
|--------|--------|
| Website | `command.agent.site_name` |
| Command | Human-readable label (map `run_scan` → "Run Malware Scan") |
| Status | Badge: `Queued` (amber) / `Running` (blue) / `Failed` (red) |
| Created | `timeAgo(command.created_at)` |

- Make rows clickable → call `onNavigate('agents')` and open that agent's detail
- Map command statuses: `pending` → "Queued", `sent` → "Running", `failed` → "Failed"

---

## 8. Infrastructure Health (Live Checks, No Placeholders)

Every status card must perform real live health checks. Never use placeholder or mock data. Auto-refresh every 30 seconds.

---

#### [MODIFY] [MonitoringAdminController.php](file:///c:/laragon/www/a-monster-panel/app/Features/Monitoring/Controllers/MonitoringAdminController.php)

Expand `getHealthStatus()` to check:

| Monitor | Check Method | Statuses |
|---------|-------------|----------|
| Database | `DB::connection()->getPdo()` | Healthy / Error |
| Redis/Cache | `Cache::put()` + `Cache::get()` | Healthy / Offline / Not Configured |
| Queue Worker | Check `jobs` table + `failed_jobs` count | Healthy / Warning (N failed) / Error |
| Scheduler | Check if `schedule:run` has executed recently (via cache marker) | Healthy / Warning / Error |
| Telegram | `TelegramProfile::where('is_default', true)` → `getMe` API | Verified / Not Configured / Failed |
| Storage | `is_writable(storage_path())` | Writable / Read-only |
| Disk Usage | `disk_total_space()` / `disk_free_space()` | Healthy / Warning (>80%) / Critical (>95%) |
| PHP Version | `phpversion()` | Display version, warn if < 8.2 |

Status levels: `Healthy` / `Warning` / `Error` / `Not Configured`

#### [MODIFY] [DashboardView.tsx](file:///c:/laragon/www/a-monster-panel/resources/js/features/dashboard/components/DashboardView.tsx)

- Add 30-second auto-refresh for infrastructure health via `setInterval`
- Status badges with colors:
  - `Healthy` → green
  - `Warning` → amber
  - `Error` → red
  - `Not Configured` → slate/gray
- Scheduler: set a cache key on every `schedule:run` and check its freshness (last run < 2 minutes ago = healthy)

#### [MODIFY] [console.php](file:///c:/laragon/www/a-monster-panel/routes/console.php)

- Add `Cache::put('amg:scheduler:last_run', now(), 300)` inside the scheduled call to mark scheduler activity

---

## 9. Verification Plan

### Automated Tests

```bash
php artisan test
```

- Update `TelegramNotificationTest` — cover retry logic, all event types, no-env fallback removed, "not configured" failure
- Update `DashboardCrudTest` — remove client CRUD tests
- Add `DisconnectProtectionTest` — hashed secret flow, expiry, one-time use, unauthorized rejection
- Add `SecurityHeadersTest` — verify all headers present
- Add `TwoFactorAuthTest` — enable, confirm, challenge, disable, recovery codes
- Add `InfrastructureHealthTest` — verify all monitors return real data

### Frontend Build

```bash
npm run build
```

### Real-World Validation Checklist

Before marking Production Ready, every item must pass:

| # | Check | Status |
|---|-------|--------|
| 1 | Fresh WordPress installation | ☐ |
| 2 | Existing WordPress installation | ☐ |
| 3 | Plugin activation | ☐ |
| 4 | Activation Key validation | ☐ |
| 5 | Heartbeat | ☐ |
| 6 | Policy synchronization | ☐ |
| 7 | Remote commands | ☐ |
| 8 | Telegram notifications (all event types) | ☐ |
| 9 | Disconnect authorization (secret path) | ☐ |
| 10 | Disconnect authorization (policy flag path) | ☐ |
| 11 | Unauthorized disconnect rejection | ☐ |
| 12 | Secret rotation | ☐ |
| 13 | Emergency Lock | ☐ |
| 14 | Plugin Whitelist | ☐ |
| 15 | XML-RPC Protection | ☐ |
| 16 | Upload Protection | ☐ |
| 17 | Malware Scan | ☐ |
| 18 | Performance Metrics | ☐ |
| 19 | Audit Logs | ☐ |
| 20 | Under Attack Mode | ☐ |
| 21 | Plugin Update | ☐ |
| 22 | Plugin Reinstall | ☐ |
| 23 | No duplicate registrations | ☐ |
| 24 | No duplicate cron jobs | ☐ |
| 25 | No fatal PHP errors | ☐ |
| 26 | No JavaScript errors | ☐ |
| 27 | No API regressions | ☐ |
| 28 | TOTP 2FA enrollment + login | ☐ |
| 29 | Recovery codes work | ☐ |
| 30 | Trusted device management | ☐ |
| 31 | New device login notification | ☐ |
| 32 | Infrastructure health all live | ☐ |
| 33 | Security headers present | ☐ |
| 34 | Login rate limiting | ☐ |

> [!CAUTION]
> Only after **every item passes** should the project be marked as Production Ready.

---

## Summary of Changes

| Category | New Files | Modified Files | Deleted Files |
|----------|-----------|---------------|---------------|
| Migrations | 4 | — | — |
| Backend PHP | 4 (middleware, controllers) | ~15 | 2 (Client model + controller) |
| Frontend TSX | 1 (SecuritySettingsView) | ~8 | 1 (ClientsView) |
| Config | — | 2 (.env, .env.example) | — |
| **Total** | **~9** | **~25** | **3** |

New composer dependencies: `pragmarx/google2fa-laravel`, `bacon/bacon-qr-code`
