Files
cim_summary/.planning/milestones/v1.0-phases/04-frontend/04-01-PLAN.md
admin 38a0f0619d chore: complete v1.0 Analytics & Monitoring milestone
Archive milestone artifacts (roadmap, requirements, audit, phase directories)
to .planning/milestones/. Evolve PROJECT.md with validated requirements and
decision outcomes. Create MILESTONES.md and RETROSPECTIVE.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 10:34:18 -05:00

9.8 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
04-frontend 01 execute 1
frontend/src/services/adminService.ts
frontend/src/components/AlertBanner.tsx
frontend/src/components/AdminMonitoringDashboard.tsx
true
ALRT-03
ANLY-02
HLTH-01
truths artifacts key_links
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
path provides contains
frontend/src/services/adminService.ts Monitoring API client methods with typed interfaces getHealth
path provides exports
frontend/src/components/AlertBanner.tsx Global alert banner with acknowledge callback
AlertBanner
path provides exports
frontend/src/components/AdminMonitoringDashboard.tsx Health panel + analytics summary panel
AdminMonitoringDashboard
from to via pattern
frontend/src/components/AlertBanner.tsx adminService.ts AlertEvent type import import.*AlertEvent.*adminService
from to via pattern
frontend/src/components/AdminMonitoringDashboard.tsx adminService.ts getHealth and getAnalytics calls 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.

<execution_context> @/home/jonathan/.claude/get-shit-done/workflows/execute-plan.md @/home/jonathan/.claude/get-shit-done/templates/summary.md </execution_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 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):

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:

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. 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:

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:

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:
    • healthybg-green-500
    • degradedbg-yellow-500
    • downbg-red-500
    • unknownbg-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. 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

<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>

After completion, create `.planning/phases/04-frontend/04-01-SUMMARY.md`