# AG Grid Implementation Plan — MW-Core Admin Portal

## AG Grid Row-Count Capabilities

| Row Model | Row Limit | Use Case |
|---|---|---|
| **Client-Side** (default) | ~100 K rows comfortably | Data fits in browser memory; DOM-virtualised — only visible rows render |
| **Infinite Row Model** | Millions | Loads blocks on scroll via datasource callback (`getRows`) |
| **Server-Side Row Model** ⭐ Enterprise | Unlimited | AG Grid calls your server for every sort / filter / group / page; server does all work |
| **Viewport Row Model** | Unlimited | Only the visible viewport is loaded |

**Short answer:** 100 K rows on a single page in client-side mode is realistic and comfortable. For the two pages in this app that can grow without bound (audit events, webhook deliveries) the Server-Side Row Model is the right long-term approach.

---

## Survey of All Live Views

| Page | Route | Current Max Rows | Table Type | AG Grid Priority |
|---|---|---|---|---|
| **Flow Audit** | `/admin/flows/:id/audit` | 10 K+ per flow | HTML table, paginated 25 | ✅ Done |
| **Audit Stream** | `/admin/audit` | 10 K+ global | HTML table, limit 50 | ✅ Done |
| **Webhook Deliveries** | `/admin/webhooks/deliveries` | 100 K+ cumulative | HTML table, paginated 25 | 🔴 Tier 1 |
| **Idempotency** | `/admin/idempotency` | 10 K (expire naturally) | HTML table, limit 50 | 🟡 Tier 2 |
| **Dead Letter Queue** | `/admin/dlq` | 1 K | HTML table, no pagination | 🟡 Tier 2 |
| **Route Config** | `/admin/routing` | < 100 | HTML table | 🟢 Tier 3 (optional) |
| **Adapter Configs** | `/admin/adapter-configs` | < 20 | HTML table | 🟢 Tier 3 (optional) |
| **API Keys** | `/admin/api-keys` | < 100 | HTML table | 🟢 Tier 3 (optional) |
| **Tenants** | `/admin/tenants` | < 100 | HTML table | 🟢 Tier 3 (optional) |
| **Webhook Endpoints** | `/admin/webhooks/endpoints` | < 50 | HTML table | 🟢 Tier 3 (optional) |
| **Webhook Sources** | `/admin/webhooks/sources` | < 50 | HTML table | 🟢 Tier 3 (optional) |
| **CloudI Services** | `/admin/cloudi` | < 30 | HTML table | ⚪ Skip |
| **Monitoring** | `/admin/monitoring` | Aggregates only | Stat cards | ⚪ Skip |
| **Dashboard** | `/admin` | Aggregates + 50 events | Stat cards | ⚪ Skip |
| **Flows** | `/admin/flows` | < 50 | Card grid | ⚪ Skip |
| **Adapter Health** | `/admin/adapters` | < 20 | Grid cards | ⚪ Skip |

---

## Tier 1 — Webhook Deliveries (Server-Side Row Model)

**Why Server-Side:** Webhook deliveries accumulate continuously and can reach hundreds of thousands. They also have rich expandable-row details (request/response bodies) that should only be loaded on demand.

### Recommended Architecture

```
AG Grid (Server-Side Row Model)
  └─ getRows(params) ─── pushEvent("load_deliveries", {startRow, endRow, filterModel, sortModel})
                                      │
                              LiveView handle_event
                                      │
                              WebhookDeliveriesLive.query_deliveries/1
                                      │
                              Repo.all + Repo.aggregate (count)
                                      │
                              push_event("deliveries_rows", {rows, totalCount})
  └─ handleEvent("deliveries_rows") ─── params.successCallback(rows, totalCount)
```

**Files to create/modify:**
- `hooks/webhooks/WebhookDeliveriesAgGridHook.js` — Server-Side datasource
- `webhook_deliveries_live.ex` — add `handle_event("load_deliveries", ...)` responder
- `webhook_deliveries_live.html.heex` — replace table with grid + keep filter bar

**Columns:** Direction, Event Type, Source/Endpoint, Status (badge), HTTP Code, Duration, Time, Actions (Replay button via context menu)

**Key grid options:**
```js
rowModelType: "serverSide",
cacheBlockSize: 50,
maxBlocksInCache: 20,
enableRangeSelection: true,
sideBar: true,
```

**Server handler pattern (Elixir):**
```elixir
def handle_event("load_deliveries", %{"start_row" => s, "end_row" => e, "filters" => f, "sort" => sort}, socket) do
  {rows, total} = WebhookDeliveries.query(s, e, f, sort)
  {:noreply, push_event(socket, "deliveries_rows", %{rows: serialize(rows), total: total})}
end
```

---

## Tier 2a — Idempotency (Client-Side, increase page size)

**Why Client-Side:** Records expire; in practice < 5 K active at any time. Load all 5 K at once and let AG Grid handle filtering, sorting, and pagination client-side.

**Changes:**
- Increase `@page_size` to 500 (or remove limit, add `Repo.all` with a reasonable cap of 5 K)
- Create `hooks/IdempotencyAgGridHook.js`
- Replace HTML table in `idempotency_live.ex`

**Columns:** Idempotency Key, Tenant, Status (badge), Request Hash, Expires At, Created

**Bonus:** AG Grid column filter lets operators search by key/tenant without a separate search form.

---

## Tier 2b — Dead Letter Queue (Client-Side)

**Why Client-Side:** DLQ is typically small (< 1 K). But the current implementation loads ALL records into memory with no pagination — AG Grid adds sorting, filtering, and the row actions (Re-queue, Discard) become context menu items.

**Changes:**
- Create `hooks/DlqAgGridHook.js`
- Replace HTML table in `dlq_live.ex`
- Row actions: `getContextMenuItems` → `pushEvent("requeue_message", {id})` / `pushEvent("discard_message", {id})`

---

## Tier 3 — Small Config Tables (Client-Side, low priority)

These pages have < 100 rows and no pressing performance need. AG Grid adds value via:
- Column filtering / sorting without extra LiveView events
- Excel export for bulk management
- Consistent UX across the admin portal

Pages: Route Config, Adapter Configs, API Keys, Tenants, Webhook Endpoints, Webhook Sources.

Approach for all: same pattern as `AuditLogAgGridHook` — `data-rows={@rows_json}`, `updated()` refreshes grid.

---

## Hook Architecture Summary

```
hooks/
  base/
    BaseAgGridHook.js         ← mixin: setupGrid, updated, destroyed
  audit/
    AuditLogAgGridHook.js     ✅ done — /admin/audit
    FlowAuditAgGridHook.js    ✅ done — /admin/flows/:id/audit
  webhooks/
    WebhookDeliveriesAgGridHook.js   ← Tier 1 (Server-Side Row Model)
  data/
    IdempotencyAgGridHook.js         ← Tier 2a
    DlqAgGridHook.js                 ← Tier 2b
  config/                            ← Tier 3 (optional)
    RouteConfigAgGridHook.js
    ApiKeysAgGridHook.js
    TenantsAgGridHook.js
    ...
```

---

## AG Grid License

Without an enterprise license key the grid renders a watermark in the corner ("AG Grid Enterprise Trial"). To suppress it, set your license key once after registering modules:

```js
// in app.js, after ModuleRegistry.registerModules([...])
agGrid.LicenseManager.setLicenseKey("YOUR_LICENSE_KEY_HERE")
```

The watermark does not affect functionality during development/trial.
