feat: container monitoring frontend UI (PR-2)
- Custom ToastContext + ToastProvider + ToastContainer (~170 lines, no deps) - useEvents() SSE hook with exponential backoff reconnect - EventProvider context for app-wide SSE stream sharing - Event-to-toast bridge with severity mapping and deduplication - Real-time status badge updates replacing 30s polling - EventSource auth probe (401/429 detection via fetch) - 14 frontend tests (useEvents + toast-rules) Quality gates: vitest 14 passed, tsc clean, eslint clean
This commit is contained in:
@@ -82,11 +82,102 @@ All checks passed!
|
||||
3. **Delete audit row**: The FK `ON DELETE CASCADE` on `instance_events.instance_id` means the audit row for `instance.deleted` cannot survive deletion. The row is inserted before `session.delete(instance)` and is cascade-deleted on commit. The event bus publication still occurs.
|
||||
4. **SQLite test compatibility**: Used `JSON` instead of `JSONB` in the SQLAlchemy model to maintain SQLite test compatibility. The migration uses `sa.JSON()` which maps appropriately.
|
||||
|
||||
## Remaining Tasks (for PR-2 / PR-3)
|
||||
## Remaining Tasks (for PR-3)
|
||||
|
||||
- Frontend `useEvents()` hook, `ToastProvider`, `toast-rules.ts`
|
||||
- Frontend badge real-time updates + polling removal
|
||||
- Integration tests for SSE endpoint (MON-PR1-012)
|
||||
- Integration tests for lifecycle hooks
|
||||
- E2E tests
|
||||
- Performance tuning and documentation
|
||||
|
||||
---
|
||||
|
||||
# PR-2 Apply Progress: Frontend UI for Container Monitoring & Notifications
|
||||
|
||||
## TDD Cycle Evidence
|
||||
|
||||
### useEvents Hook (MON-PR2-001 + MON-PR2-002 + MON-PR2-006)
|
||||
| Cycle | Action | Evidence |
|
||||
|-------|--------|----------|
|
||||
| RED | Wrote `use-events.test.ts` with mocks for non-existent `api/events.ts` and `hooks/use-events.ts` | `vitest` collection error: `Cannot find module '../api/events'` |
|
||||
| GREEN | Implemented `types/events.ts`, `api/events.ts`, and `hooks/use-events.ts` with SSE connect + reconnect backoff | `vitest run src/hooks/use-events.test.ts` → 7 passed |
|
||||
| REFACTOR | Extracted `probeEventStreamStatus` into `api/events.ts`; added `isMountedRef` guard to prevent state updates after unmount | Tests still pass |
|
||||
| TRIANGULATE | Added 401 redirect test and 429 penalty test using `probeEventStreamStatus` | Both pass |
|
||||
|
||||
### Toast Rules (MON-PR2-003 + MON-PR2-007)
|
||||
| Cycle | Action | Evidence |
|
||||
|-------|--------|----------|
|
||||
| RED | Wrote `toast-rules.test.ts` mocking `../state/toast` | `vitest` collection error: `Cannot find module '../state/toast'` |
|
||||
| GREEN | Implemented `state/toast.tsx` (custom toast system) and `components/toast-rules.ts` | `vitest run src/components/toast-rules.test.ts` → 7 passed |
|
||||
| TRIANGULATE | Added deduplication tests (within 1s and after 1s) | Tests pass |
|
||||
|
||||
### EventProvider + Integration (MON-PR2-004 + MON-PR2-005 + MON-PR2-007)
|
||||
| Cycle | Action | Evidence |
|
||||
|-------|--------|----------|
|
||||
| RED | Attempted to mount `<ToastProvider>` in `app-shell.tsx` before component existed | Build error: `Cannot find module '../state/toast'` |
|
||||
| GREEN | Created `state/events.tsx`, `components/event-toast-bridge.tsx`, and integrated all providers into `app-shell.tsx` | `tsc --noEmit` clean; app renders in tests |
|
||||
|
||||
## Completed Tasks
|
||||
|
||||
- [x] MON-PR2-001: `useEvents()` SSE hook with reconnect backoff (`apps/web/src/hooks/use-events.ts`)
|
||||
- [x] MON-PR2-002: `events.ts` API client — EventSource wrapper + probe helper (`apps/web/src/api/events.ts`)
|
||||
- [x] MON-PR2-003: Custom `Toast` system with severity, auto-dismiss, manual dismiss (`apps/web/src/state/toast.tsx`)
|
||||
- [x] MON-PR2-004: `ToastContainer` that manages toast queue + stacking + CSS animations
|
||||
- [x] MON-PR2-005: `EventProvider` context — wraps app, provides shared event stream
|
||||
- [x] MON-PR2-006: Real-time status badge updates — replaced 30s polling in `instance-list.tsx` with SSE-driven updates
|
||||
- [x] MON-PR2-007: Integrated into `app-shell.tsx` — mounts `ToastProvider` + `EventProvider` + `EventToastBridge`
|
||||
- [x] MON-PR2-008: Frontend tests for `useEvents` (7 tests) and toast rules (7 tests)
|
||||
|
||||
## Files Changed
|
||||
|
||||
### New Files
|
||||
- `apps/web/src/types/events.ts`
|
||||
- `apps/web/src/api/events.ts`
|
||||
- `apps/web/src/hooks/use-events.ts`
|
||||
- `apps/web/src/hooks/use-events.test.ts`
|
||||
- `apps/web/src/components/toast-rules.ts`
|
||||
- `apps/web/src/components/toast-rules.test.ts`
|
||||
- `apps/web/src/state/toast.tsx`
|
||||
- `apps/web/src/state/events.tsx`
|
||||
- `apps/web/src/components/event-toast-bridge.tsx`
|
||||
|
||||
### Modified Files
|
||||
- `apps/web/src/components/app-shell.tsx` — mount providers
|
||||
- `apps/web/src/components/instance-list.tsx` — remove 30s polling, add SSE status updates, add 60s refresh
|
||||
- `apps/web/src/components/session-card.tsx` — update badge color mapping
|
||||
- `apps/web/src/styles.css` — add status-badge and toast animation styles
|
||||
|
||||
## Test Evidence
|
||||
|
||||
```bash
|
||||
# New frontend unit tests — all pass
|
||||
$ cd apps/web && npx vitest run src/hooks/use-events.test.ts src/components/toast-rules.test.ts
|
||||
14 passed
|
||||
|
||||
# Regression check on related pages/components
|
||||
$ cd apps/web && npx vitest run src/hooks/use-events.test.ts src/components/toast-rules.test.ts src/pages/dashboard.test.tsx src/components/terminal-session-tabs.test.tsx src/components/protected-route.test.tsx
|
||||
25 passed
|
||||
|
||||
# TypeScript check
|
||||
$ cd apps/web && npx tsc --noEmit
|
||||
# Exit code: 0
|
||||
|
||||
# Lint on new/modified files
|
||||
$ cd apps/web && npx eslint <new ts/tsx files> --ext ts,tsx --report-unused-disable-directives --max-warnings 0
|
||||
# Exit code: 0
|
||||
```
|
||||
|
||||
## Deviation from Design
|
||||
|
||||
1. **No sonner dependency**: The orchestrator explicitly instructed not to install `sonner` because `npm install` hangs in this environment. Implemented a custom ~170-line toast system instead using pure React + inline CSS. It is API-compatible with the expected `toast.info/success/warning/error(message, opts)` contract.
|
||||
2. **EventSource 401/429 detection**: Native `EventSource.onerror` does not expose HTTP status codes. Added `probeEventStreamStatus()` in `api/events.ts` that performs a short `fetch()` with `AbortController` timeout to detect 401/429 before reconnecting.
|
||||
3. **App.tsx vs app-shell.tsx**: This codebase has no `App.tsx`; `AppShell` in `app-shell.tsx` is the layout component that wraps all authenticated routes. Providers were mounted there instead.
|
||||
4. **Tunnel health badge removed from instance-list**: The 30s polling loop was the sole source of tunnel health data. After removing it, the "tunnel error" badge is redundant because SSE status transitions to `unhealthy` are reflected in the status badge itself.
|
||||
|
||||
## Remaining Tasks (for PR-3)
|
||||
|
||||
- Integration tests for SSE endpoint (MON-PR1-012)
|
||||
- Integration tests for lifecycle hooks (MON-PR3-001)
|
||||
- E2E tests for container start → toast and crash detection (MON-PR3-002 / MON-PR3-003)
|
||||
- Performance tuning — connection limits, queue bounds, jitter (MON-PR3-004)
|
||||
- Documentation updates (MON-PR3-005)
|
||||
- Final cleanup and regression validation (MON-PR3-006)
|
||||
|
||||
Reference in New Issue
Block a user