diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 71615f3..4fb9619 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -77,7 +77,11 @@ Plans: 2. The admin dashboard shows health status indicators (green/yellow/red) for all four services, with the last-checked timestamp visible 3. The admin dashboard shows processing metrics (upload counts, success/failure rates, average processing time) sourced from the persistent Supabase backend 4. A non-admin user visiting the admin route is redirected or shown an access-denied state -**Plans**: TBD +**Plans:** 2 plans + +Plans: +- [ ] 04-01-PLAN.md — AdminService monitoring methods + AlertBanner + AdminMonitoringDashboard components +- [ ] 04-02-PLAN.md — Wire components into Dashboard + visual verification checkpoint ## Progress diff --git a/.planning/phases/04-frontend/04-01-PLAN.md b/.planning/phases/04-frontend/04-01-PLAN.md new file mode 100644 index 0000000..08e4002 --- /dev/null +++ b/.planning/phases/04-frontend/04-01-PLAN.md @@ -0,0 +1,239 @@ +--- +phase: 04-frontend +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - frontend/src/services/adminService.ts + - frontend/src/components/AlertBanner.tsx + - frontend/src/components/AdminMonitoringDashboard.tsx +autonomous: true +requirements: + - ALRT-03 + - ANLY-02 + - HLTH-01 + +must_haves: + truths: + - "adminService exposes typed methods for getHealth(), getAnalytics(range), getAlerts(), and acknowledgeAlert(id)" + - "AlertBanner component renders critical active alerts with acknowledge button" + - "AdminMonitoringDashboard component shows health status grid and analytics summary with range selector" + artifacts: + - path: "frontend/src/services/adminService.ts" + provides: "Monitoring API client methods with typed interfaces" + contains: "getHealth" + - path: "frontend/src/components/AlertBanner.tsx" + provides: "Global alert banner with acknowledge callback" + exports: ["AlertBanner"] + - path: "frontend/src/components/AdminMonitoringDashboard.tsx" + provides: "Health panel + analytics summary panel" + exports: ["AdminMonitoringDashboard"] + key_links: + - from: "frontend/src/components/AlertBanner.tsx" + to: "adminService.ts" + via: "AlertEvent type import" + pattern: "import.*AlertEvent.*adminService" + - from: "frontend/src/components/AdminMonitoringDashboard.tsx" + to: "adminService.ts" + via: "getHealth and getAnalytics calls" + pattern: "adminService\\.(getHealth|getAnalytics)" +--- + + +Create the three building blocks for Phase 4 frontend: extend adminService with typed monitoring API methods, build the AlertBanner component, and build the AdminMonitoringDashboard component. + +Purpose: These components and service methods are the foundation that Plan 02 wires into the Dashboard. Separating creation from wiring keeps each plan focused. +Output: Three files ready to be imported and mounted in App.tsx. + + + +@/home/jonathan/.claude/get-shit-done/workflows/execute-plan.md +@/home/jonathan/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/04-frontend/04-RESEARCH.md +@frontend/src/services/adminService.ts +@frontend/src/components/Analytics.tsx +@frontend/src/components/UploadMonitoringDashboard.tsx +@frontend/src/utils/cn.ts + + + + + + Task 1: Extend adminService with monitoring API methods and types + frontend/src/services/adminService.ts + +Add three exported interfaces and four new methods to the existing AdminService class in `frontend/src/services/adminService.ts`. + +**Interfaces to add** (above the AdminService class): + +```typescript +export interface AlertEvent { + id: string; + service_name: string; + alert_type: 'service_down' | 'service_degraded' | 'recovery'; + status: 'active' | 'acknowledged' | 'resolved'; + message: string | null; + details: Record | null; + created_at: string; + acknowledged_at: string | null; + resolved_at: string | null; +} + +export interface ServiceHealthEntry { + service: string; + status: 'healthy' | 'degraded' | 'down' | 'unknown'; + checkedAt: string | null; + latencyMs: number | null; + errorMessage: string | null; +} + +export interface AnalyticsSummary { + range: string; + totalUploads: number; + succeeded: number; + failed: number; + successRate: number; + avgProcessingMs: number | null; + generatedAt: string; +} +``` + +**IMPORTANT type casing note** (from RESEARCH Pitfall 4): +- `ServiceHealthEntry` uses camelCase (backend admin.ts remaps to camelCase) +- `AlertEvent` uses snake_case (backend returns raw model data) +- `AnalyticsSummary` uses camelCase (from backend analyticsService.ts) + +**Methods to add** inside the AdminService class: + +```typescript +async getHealth(): Promise { + const response = await apiClient.get('/admin/health'); + return response.data.data; +} + +async getAnalytics(range: string = '24h'): Promise { + const response = await apiClient.get(`/admin/analytics?range=${range}`); + return response.data.data; +} + +async getAlerts(): Promise { + const response = await apiClient.get('/admin/alerts'); + return response.data.data; +} + +async acknowledgeAlert(id: string): Promise { + const response = await apiClient.post(`/admin/alerts/${id}/acknowledge`); + return response.data.data; +} +``` + +Keep all existing methods and interfaces. Do not modify the `apiClient` interceptor or the `ADMIN_EMAIL` check. + + + cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit --strict src/services/adminService.ts 2>&1 | head -20 + Verify the file exports AlertEvent, ServiceHealthEntry, AnalyticsSummary interfaces and the four new methods + + adminService.ts exports 3 new typed interfaces and 4 new methods (getHealth, getAnalytics, getAlerts, acknowledgeAlert) alongside all existing functionality + + + + Task 2: Create AlertBanner and AdminMonitoringDashboard components + frontend/src/components/AlertBanner.tsx, frontend/src/components/AdminMonitoringDashboard.tsx + +**AlertBanner.tsx** — Create a new component at `frontend/src/components/AlertBanner.tsx`: + +Props interface: +```typescript +interface AlertBannerProps { + alerts: AlertEvent[]; + onAcknowledge: (id: string) => Promise; +} +``` + +Behavior: +- Filter alerts to show only `status === 'active'` AND `alert_type` is `service_down` or `service_degraded` (per RESEARCH Pitfall — `recovery` is informational, not critical) +- If no critical alerts after filtering, return `null` +- Render a red banner (`bg-red-600 px-4 py-3`) with each alert showing: + - `AlertTriangle` icon from lucide-react (h-5 w-5, flex-shrink-0) + - Text: `{alert.service_name}: {alert.message ?? alert.alert_type}` (text-sm font-medium text-white) + - "Acknowledge" button with `X` icon from lucide-react (text-sm underline hover:no-underline) +- `onAcknowledge` called with `alert.id` on button click +- Import `AlertEvent` from `../services/adminService` +- Import `cn` from `../utils/cn` +- Use `AlertTriangle` and `X` from `lucide-react` + +**AdminMonitoringDashboard.tsx** — Create at `frontend/src/components/AdminMonitoringDashboard.tsx`: + +This component contains two sections: Service Health Panel and Processing Analytics Panel. + +State: +```typescript +const [health, setHealth] = useState([]); +const [analytics, setAnalytics] = useState(null); +const [range, setRange] = useState('24h'); +const [loading, setLoading] = useState(true); +const [error, setError] = useState(null); +``` + +Data fetching: Use `useCallback` + `useEffect` pattern matching existing `Analytics.tsx`: +- `loadData()` calls `Promise.all([adminService.getHealth(), adminService.getAnalytics(range)])` +- Sets loading/error state appropriately +- Re-fetches when `range` changes + +**Service Health Panel:** +- 2x2 grid on desktop (`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4`) +- Each card: white bg, rounded-lg, shadow-soft, border border-gray-100, p-4 +- Status dot: `w-3 h-3 rounded-full` with color mapping: + - `healthy` → `bg-green-500` + - `degraded` → `bg-yellow-500` + - `down` → `bg-red-500` + - `unknown` → `bg-gray-400` +- Service display name mapping: `document_ai` → "Document AI", `llm_api` → "LLM API", `supabase` → "Supabase", `firebase_auth` → "Firebase Auth" +- Show `checkedAt` as `new Date(checkedAt).toLocaleString()` if available, otherwise "Never checked" +- Show `latencyMs` with "ms" suffix if available +- Use `Activity`, `Clock` icons from lucide-react + +**Processing Analytics Panel:** +- Range selector: `` with options `24h`, `7d`, `30d` — onChange updates `range` state +- Stat cards in 1x5 grid: Total Uploads, Succeeded, Failed, Success Rate (formatted as `(successRate * 100).toFixed(1)%`), Avg Processing Time (format `avgProcessingMs` as seconds: `(avgProcessingMs / 1000).toFixed(1)s`, or "N/A" if null) +- Include a "Refresh" button that calls `loadData()` — matches existing Analytics.tsx refresh pattern +- Use `RefreshCw` icon from lucide-react for refresh button + +Loading state: Show `animate-spin rounded-full h-8 w-8 border-b-2 border-accent-500` centered (matching existing App.tsx pattern). +Error state: Show error message with retry button. + +Import `ServiceHealthEntry`, `AnalyticsSummary` from `../services/adminService`. +Import `adminService` from `../services/adminService`. +Import `cn` from `../utils/cn`. + + + cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit 2>&1 | head -30 + Check that AlertBanner.tsx and AdminMonitoringDashboard.tsx exist and export their components + + AlertBanner renders critical alerts with acknowledge buttons; AdminMonitoringDashboard renders health status grid with colored dots and analytics summary with range selector and refresh + + + + + +- `npx tsc --noEmit` passes with no type errors in the three modified/created files +- AlertBanner exports a React component accepting `alerts` and `onAcknowledge` props +- AdminMonitoringDashboard exports a React component with no required props +- adminService exports AlertEvent, ServiceHealthEntry, AnalyticsSummary interfaces +- adminService.getHealth(), getAnalytics(), getAlerts(), acknowledgeAlert() methods exist with correct return types + + + +All three files compile without TypeScript errors. Components follow existing project patterns (Tailwind, lucide-react, cn utility). Types match backend API response shapes exactly (camelCase for health/analytics, snake_case for alerts). + + + +After completion, create `.planning/phases/04-frontend/04-01-SUMMARY.md` + diff --git a/.planning/phases/04-frontend/04-02-PLAN.md b/.planning/phases/04-frontend/04-02-PLAN.md new file mode 100644 index 0000000..a6e39a9 --- /dev/null +++ b/.planning/phases/04-frontend/04-02-PLAN.md @@ -0,0 +1,187 @@ +--- +phase: 04-frontend +plan: 02 +type: execute +wave: 2 +depends_on: + - 04-01 +files_modified: + - frontend/src/App.tsx +autonomous: false +requirements: + - ALRT-03 + - ANLY-02 + - HLTH-01 + +must_haves: + truths: + - "Alert banner appears above tab navigation when there are active critical alerts" + - "Alert banner disappears immediately after admin clicks Acknowledge (optimistic update)" + - "Monitoring tab shows health status indicators and processing analytics from Supabase backend" + - "Non-admin user on monitoring tab sees Access Denied, not the dashboard" + - "Alert fetching only happens for admin users (gated by isAdmin check)" + artifacts: + - path: "frontend/src/App.tsx" + provides: "Dashboard with AlertBanner wired above nav and AdminMonitoringDashboard in monitoring tab" + contains: "AlertBanner" + key_links: + - from: "frontend/src/App.tsx" + to: "frontend/src/components/AlertBanner.tsx" + via: "import and render above nav" + pattern: "import.*AlertBanner" + - from: "frontend/src/App.tsx" + to: "frontend/src/components/AdminMonitoringDashboard.tsx" + via: "import and render in monitoring tab" + pattern: "import.*AdminMonitoringDashboard" + - from: "frontend/src/App.tsx" + to: "frontend/src/services/adminService.ts" + via: "getAlerts call in Dashboard useEffect" + pattern: "adminService\\.getAlerts" +--- + + +Wire AlertBanner and AdminMonitoringDashboard into the Dashboard component in App.tsx. Add alert state management with optimistic acknowledge. Replace the monitoring tab content from UploadMonitoringDashboard to AdminMonitoringDashboard. + +Purpose: This completes the frontend delivery of ALRT-03 (in-app alert banner), ANLY-02 (processing metrics UI), and HLTH-01 (health status UI). +Output: Fully wired admin monitoring UI visible in the application. + + + +@/home/jonathan/.claude/get-shit-done/workflows/execute-plan.md +@/home/jonathan/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/04-frontend/04-RESEARCH.md +@.planning/phases/04-frontend/04-01-SUMMARY.md +@frontend/src/App.tsx + + + + + + Task 1: Wire AlertBanner and AdminMonitoringDashboard into Dashboard + frontend/src/App.tsx + +Modify the Dashboard component in `frontend/src/App.tsx` with these changes: + +**1. Add imports** (at the top of the file): +```typescript +import AlertBanner from './components/AlertBanner'; +import AdminMonitoringDashboard from './components/AdminMonitoringDashboard'; +``` +Import `AlertEvent` type from `./services/adminService`. + +The `UploadMonitoringDashboard` import can be removed since the monitoring tab will now use `AdminMonitoringDashboard`. + +**2. Add alert state** inside the Dashboard component, after the existing state declarations: +```typescript +const [activeAlerts, setActiveAlerts] = useState([]); +``` + +**3. Add alert fetching useEffect** — MUST be gated by `isAdmin` (RESEARCH Pitfall 5): +```typescript +useEffect(() => { + if (isAdmin) { + adminService.getAlerts().then(setActiveAlerts).catch(() => {}); + } +}, [isAdmin]); +``` + +**4. Add handleAcknowledge callback** — uses optimistic update (RESEARCH Pitfall 2): +```typescript +const handleAcknowledge = async (id: string) => { + setActiveAlerts(prev => prev.filter(a => a.id !== id)); + try { + await adminService.acknowledgeAlert(id); + } catch { + // On failure, re-fetch to restore correct state + adminService.getAlerts().then(setActiveAlerts).catch(() => {}); + } +}; +``` + +**5. Render AlertBanner ABOVE the `` element** (RESEARCH Pitfall 1 — must be above nav, not inside a tab): +Inside the Dashboard return JSX, immediately after `` and before the `{/* Navigation */}` comment and `` element, add: +```jsx +{isAdmin && activeAlerts.length > 0 && ( + +)} +``` + +**6. Replace monitoring tab content:** +Change: +```jsx +{activeTab === 'monitoring' && isAdmin && ( + +)} +``` +To: +```jsx +{activeTab === 'monitoring' && isAdmin && ( + +)} +``` + +**7. Keep the existing non-admin access-denied fallback** for `{activeTab === 'monitoring' && !isAdmin && (...)}` — do not change it. + +**Do NOT change:** +- The `analytics` tab content (still renders existing ``) +- Any other tab content +- The tab navigation buttons +- Any other Dashboard state or logic + + + cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit 2>&1 | head -30 + Run `npm run dev` and verify: (1) AlertBanner shows above nav if alerts exist, (2) Monitoring tab shows health grid and analytics panel, (3) Non-admin sees Access Denied on monitoring tab + + AlertBanner renders above nav for admin users with active alerts; monitoring tab shows AdminMonitoringDashboard with health status and analytics; non-admin access denied preserved + + + + Task 2: Visual verification of monitoring UI + + Complete admin monitoring frontend: + 1. AlertBanner above navigation (shows when critical alerts exist) + 2. AdminMonitoringDashboard in Monitoring tab (health status grid + analytics summary) + 3. Alert acknowledge with optimistic update + + + 1. Start frontend: `cd frontend && npm run dev` + 2. Start backend: `cd backend && npm run dev` + 3. Log in as admin (jpressnell@bluepointcapital.com) + 4. Click the "Monitoring" tab — verify: + - Health status cards show for all 4 services (Document AI, LLM API, Supabase, Firebase Auth) + - Each card has colored dot (green/yellow/red/gray) and last-checked timestamp + - Analytics section shows upload counts, success/failure rates, avg processing time + - Range selector (24h/7d/30d) changes the analytics data + - Refresh button reloads data + 5. If there are active alerts: verify red banner appears ABOVE the tab navigation on ALL tabs (not just Monitoring) + 6. If no alerts exist: verify no banner is shown (this is correct behavior) + 7. (Optional) If you can trigger an alert (e.g., by having a service probe fail), verify the banner appears and "Acknowledge" button removes it immediately + + Type "approved" to complete Phase 4, or describe any issues to fix + + + + + +- `npx tsc --noEmit` passes with no errors +- AlertBanner renders above `` in Dashboard (not inside a tab) +- Alert state only fetched when `isAdmin` is true +- Optimistic update: banner disappears immediately on acknowledge click +- Monitoring tab renders AdminMonitoringDashboard (not UploadMonitoringDashboard) +- Non-admin monitoring access denied fallback still works +- `npm run build` completes successfully + + + +Admin user sees health indicators and processing metrics on the Monitoring tab. Alert banner appears above navigation when active critical alerts exist. Acknowledge removes the alert banner immediately. Non-admin users see Access Denied on admin tabs. + + + +After completion, create `.planning/phases/04-frontend/04-02-SUMMARY.md` +