7.2 KiB
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:
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
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:
// 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:
// 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:
- Simple: Download Vue, Chart.js, Tailwind to
web/vendor/and serve statically - 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:
- Remove
@applyand use inline Tailwind classes in the HTML - 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:
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)
<!-- BEFORE -->
<iframe src="http://localhost:3000" ...></iframe>
<!-- AFTER -->
<iframe :src="grafanaUrl" ...></iframe>
// 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:
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 --workspacepasses 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 <script> or <link> tags referencing external CDN URLs |
test_csrf_token_present |
tests/e2e/admin_ui.rs or Playwright |
Mutating requests include a CSRF token |
2. Manual / Visual Verification
- Login with correct password → dashboard loads
- Login with wrong password → error message shown
- Logout → redirected to login, session cookie cleared
- Service role key never appears in browser DevTools (Network, Application tabs)
- Auth tab shows user list with working search
- Storage tab allows deleting objects
- Grafana iframe loads from configured URL
- Delete project shows confirmation dialog
- Works in air-gapped environment (no CDN dependencies)
- Responsive layout works on 1024px and 1440px viewports
- Error toast appears on API failures (e.g., network down)
3. CI Gate
cargo test --workspacegreen (backend)- E2E tests (Playwright or equivalent) run in CI against a
docker compose upstack - Static analysis confirms no external CDN references in
web/HTML/JS files - All destructive API calls in the UI are confirmed via a dialog (code review checklist)