docs(04-frontend): create phase plan
This commit is contained in:
@@ -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
|
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
|
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
|
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
|
## Progress
|
||||||
|
|
||||||
|
|||||||
239
.planning/phases/04-frontend/04-01-PLAN.md
Normal file
239
.planning/phases/04-frontend/04-01-PLAN.md
Normal file
@@ -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)"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/jonathan/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/jonathan/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Extend adminService with monitoring API methods and types</name>
|
||||||
|
<files>frontend/src/services/adminService.ts</files>
|
||||||
|
<action>
|
||||||
|
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<string, unknown> | 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<ServiceHealthEntry[]> {
|
||||||
|
const response = await apiClient.get('/admin/health');
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAnalytics(range: string = '24h'): Promise<AnalyticsSummary> {
|
||||||
|
const response = await apiClient.get(`/admin/analytics?range=${range}`);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAlerts(): Promise<AlertEvent[]> {
|
||||||
|
const response = await apiClient.get('/admin/alerts');
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async acknowledgeAlert(id: string): Promise<AlertEvent> {
|
||||||
|
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.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit --strict src/services/adminService.ts 2>&1 | head -20</automated>
|
||||||
|
<manual>Verify the file exports AlertEvent, ServiceHealthEntry, AnalyticsSummary interfaces and the four new methods</manual>
|
||||||
|
</verify>
|
||||||
|
<done>adminService.ts exports 3 new typed interfaces and 4 new methods (getHealth, getAnalytics, getAlerts, acknowledgeAlert) alongside all existing functionality</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Create AlertBanner and AdminMonitoringDashboard components</name>
|
||||||
|
<files>frontend/src/components/AlertBanner.tsx, frontend/src/components/AdminMonitoringDashboard.tsx</files>
|
||||||
|
<action>
|
||||||
|
**AlertBanner.tsx** — Create a new component at `frontend/src/components/AlertBanner.tsx`:
|
||||||
|
|
||||||
|
Props interface:
|
||||||
|
```typescript
|
||||||
|
interface AlertBannerProps {
|
||||||
|
alerts: AlertEvent[];
|
||||||
|
onAcknowledge: (id: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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<ServiceHealthEntry[]>([]);
|
||||||
|
const [analytics, setAnalytics] = useState<AnalyticsSummary | null>(null);
|
||||||
|
const [range, setRange] = useState('24h');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(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: `<select>` 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`.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit 2>&1 | head -30</automated>
|
||||||
|
<manual>Check that AlertBanner.tsx and AdminMonitoringDashboard.tsx exist and export their components</manual>
|
||||||
|
</verify>
|
||||||
|
<done>AlertBanner renders critical alerts with acknowledge buttons; AdminMonitoringDashboard renders health status grid with colored dots and analytics summary with range selector and refresh</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- `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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
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).
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/04-frontend/04-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
187
.planning/phases/04-frontend/04-02-PLAN.md
Normal file
187
.planning/phases/04-frontend/04-02-PLAN.md
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/jonathan/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/jonathan/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Wire AlertBanner and AdminMonitoringDashboard into Dashboard</name>
|
||||||
|
<files>frontend/src/App.tsx</files>
|
||||||
|
<action>
|
||||||
|
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<AlertEvent[]>([]);
|
||||||
|
```
|
||||||
|
|
||||||
|
**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 `<nav>` element** (RESEARCH Pitfall 1 — must be above nav, not inside a tab):
|
||||||
|
Inside the Dashboard return JSX, immediately after `<div className="min-h-screen bg-gray-50">` and before the `{/* Navigation */}` comment and `<nav>` element, add:
|
||||||
|
```jsx
|
||||||
|
{isAdmin && activeAlerts.length > 0 && (
|
||||||
|
<AlertBanner alerts={activeAlerts} onAcknowledge={handleAcknowledge} />
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
**6. Replace monitoring tab content:**
|
||||||
|
Change:
|
||||||
|
```jsx
|
||||||
|
{activeTab === 'monitoring' && isAdmin && (
|
||||||
|
<UploadMonitoringDashboard />
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
To:
|
||||||
|
```jsx
|
||||||
|
{activeTab === 'monitoring' && isAdmin && (
|
||||||
|
<AdminMonitoringDashboard />
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
**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 `<Analytics />`)
|
||||||
|
- Any other tab content
|
||||||
|
- The tab navigation buttons
|
||||||
|
- Any other Dashboard state or logic
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jonathan/Coding/cim_summary/frontend && npx tsc --noEmit 2>&1 | head -30</automated>
|
||||||
|
<manual>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</manual>
|
||||||
|
</verify>
|
||||||
|
<done>AlertBanner renders above nav for admin users with active alerts; monitoring tab shows AdminMonitoringDashboard with health status and analytics; non-admin access denied preserved</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="checkpoint:human-verify" gate="blocking">
|
||||||
|
<name>Task 2: Visual verification of monitoring UI</name>
|
||||||
|
<what-built>
|
||||||
|
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
|
||||||
|
</what-built>
|
||||||
|
<how-to-verify>
|
||||||
|
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
|
||||||
|
</how-to-verify>
|
||||||
|
<resume-signal>Type "approved" to complete Phase 4, or describe any issues to fix</resume-signal>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- `npx tsc --noEmit` passes with no errors
|
||||||
|
- AlertBanner renders above `<nav>` 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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
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.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/04-frontend/04-02-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user