# Milestone 10: Admin UI **Goal:** MadBase Studio is a functional admin dashboard for core operations. **Depends on:** M0 (Security), M1 (Foundation), M3 (Auth), M9 (Control Plane) --- ## 10.1 — Authentication ### 10.1.1 Real login form **File:** `web/js/admin.js` Replace the current auth check (hitting `/platform/v1/projects` and checking for 401) with a proper login flow: ```javascript async login() { const resp = await fetch('/platform/v1/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: this.loginPassword }), }); if (resp.ok) { this.isAuthenticated = true; this.loginError = ''; await this.loadDashboard(); } else { this.loginError = 'Invalid password'; } } ``` The server sets an `HttpOnly` session cookie on success (implemented in M0). ### 10.1.2 Add logout ```javascript async logout() { await fetch('/platform/v1/logout', { method: 'POST' }); this.isAuthenticated = false; // Clear all reactive state } ``` ### 10.1.3 CSRF protection Generate a CSRF token on page load, include in all mutation requests: ```javascript // On page load const csrfResp = await fetch('/platform/v1/csrf-token'); this.csrfToken = (await csrfResp.json()).token; // On mutations headers: { 'X-CSRF-Token': this.csrfToken } ``` --- ## 10.2 — Security Fixes ### 10.2.1 Stop sending service role key to browser **File:** `web/js/admin.js` line ~121 Remove `fetchAdminConfig()` and the `serviceRoleKey` reactive variable. All admin API calls should use session auth (the `HttpOnly` cookie), not the service role key. Replace storage/data API calls that use the service role key with admin-proxied endpoints: ```javascript // BEFORE headers: { 'Authorization': `Bearer ${this.serviceRoleKey}` } // AFTER — use session cookie (automatic with same-origin requests) // No Authorization header needed for /platform/v1/* routes ``` ### 10.2.2 Bundle CDN dependencies Replace CDN script tags with locally bundled files. Options: 1. **Simple:** Download Vue, Chart.js, Tailwind to `web/vendor/` and serve statically 2. **Better:** Add a minimal build step with Vite that bundles everything into `web/dist/` For air-gapped deployments, option 1 is essential. ### 10.2.3 Fix Tailwind @apply **File:** `web/css/admin.css` `@apply` directives don't work with CDN Tailwind JIT. Either: 1. Remove `@apply` and use inline Tailwind classes in the HTML 2. Or add a build step that processes the CSS with Tailwind CLI --- ## 10.3 — Missing Views ### 10.3.1 Auth management tab Add a view showing: - User list with search/filter - User detail: email, created_at, confirmed_at, last_sign_in_at, providers - Actions: ban/unban, confirm email, delete user, reset password ### 10.3.2 Realtime console Add a view showing: - Active WebSocket connections count - Active channel subscriptions - Live event stream (filterable by table/event type) - Presence information per channel ### 10.3.3 Object deletion in Storage view Add a delete button next to each object in the storage file browser: ```javascript async deleteObject(bucketId, objectName) { if (!confirm(`Delete ${objectName}?`)) return; await fetch(`/platform/v1/storage/${bucketId}/${objectName}`, { method: 'DELETE' }); await this.fetchObjects(bucketId); } ``` --- ## 10.4 — Usability ### 10.4.1 Configurable Grafana URL **File:** `web/admin.html` — Grafana iframe (line ~414) ```html ``` ```javascript // In admin.js data grafanaUrl: window.MADBASE_GRAFANA_URL || '/grafana', ``` Set via env var or server-rendered config. ### 10.4.2 Confirmation dialogs Add `confirm()` before all destructive operations: - Delete project - Delete user - Delete storage object - Remove server ### 10.4.3 Error handling Add global error display: ```javascript methods: { async apiCall(url, options) { try { const resp = await fetch(url, options); if (!resp.ok) { const err = await resp.json(); this.showError(err.error || 'Request failed'); return null; } return resp; } catch (e) { this.showError(e.message); return null; } }, showError(msg) { this.errorMessage = msg; setTimeout(() => this.errorMessage = '', 5000); } } ``` --- ## Completion Requirements This milestone is **not complete** until every item below is satisfied. ### 1. Full Test Suite — All Green - [ ] `cargo test --workspace` passes with **zero failures** (backend unchanged, but verify no regressions) - [ ] All **pre-existing tests** still pass - [ ] **New end-to-end / browser tests** cover the admin UI: | Test | Location | What it validates | |------|----------|-------------------| | `test_login_success` | `tests/e2e/admin_ui.rs` or Playwright | Correct password → dashboard loads, session cookie set | | `test_login_failure` | `tests/e2e/admin_ui.rs` or Playwright | Wrong password → error message shown, no cookie | | `test_logout` | `tests/e2e/admin_ui.rs` or Playwright | Logout → redirected to login, session cookie cleared | | `test_no_service_key_in_client` | `tests/e2e/admin_ui.rs` or Playwright | Service role key absent from page source and network requests | | `test_auth_user_list` | `tests/e2e/admin_ui.rs` or Playwright | Auth tab renders user list from API | | `test_auth_user_search` | `tests/e2e/admin_ui.rs` or Playwright | Typing in search filters the user list | | `test_storage_delete_object` | `tests/e2e/admin_ui.rs` or Playwright | Delete button removes object; confirm dialog appears first | | `test_grafana_iframe_configurable` | `tests/e2e/admin_ui.rs` or Playwright | Iframe `src` matches configured `MADBASE_GRAFANA_URL` | | `test_delete_project_confirmation` | `tests/e2e/admin_ui.rs` or Playwright | Delete project requires confirmation dialog | | `test_no_cdn_dependencies` | `web/admin.html` (static analysis) | No `