diff --git a/ui/src/components/health/health-card.tsx b/ui/src/components/health/health-card.tsx
new file mode 100644
index 00000000..c3097596
--- /dev/null
+++ b/ui/src/components/health/health-card.tsx
@@ -0,0 +1,68 @@
+import { Card, CardContent } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { CheckCircle, AlertTriangle, XCircle, Wrench } from 'lucide-react';
+import { useFixHealth } from '@/hooks/use-health';
+
+interface HealthCheck {
+ id: string;
+ name: string;
+ status: 'ok' | 'warning' | 'error';
+ message: string;
+ details?: string;
+ fixable?: boolean;
+}
+
+const statusConfig = {
+ ok: {
+ icon: CheckCircle,
+ color: 'text-green-600',
+ bg: 'bg-green-50 dark:bg-green-900/20',
+ border: 'border-green-200 dark:border-green-800',
+ },
+ warning: {
+ icon: AlertTriangle,
+ color: 'text-yellow-500',
+ bg: 'bg-yellow-50 dark:bg-yellow-900/20',
+ border: 'border-yellow-200 dark:border-yellow-800',
+ },
+ error: {
+ icon: XCircle,
+ color: 'text-red-500',
+ bg: 'bg-red-50 dark:bg-red-900/20',
+ border: 'border-red-200 dark:border-red-800',
+ },
+};
+
+export function HealthCard({ check }: { check: HealthCheck }) {
+ const fixMutation = useFixHealth();
+ const config = statusConfig[check.status];
+ const Icon = config.icon;
+
+ return (
+
+
+
+
+
+ {check.name}
+
+ {check.fixable && check.status !== 'ok' && (
+
+ )}
+
+ {check.message}
+ {check.details && (
+ {check.details}
+ )}
+
+
+ );
+}
diff --git a/ui/src/components/health/health-check-item.tsx b/ui/src/components/health/health-check-item.tsx
new file mode 100644
index 00000000..88268d64
--- /dev/null
+++ b/ui/src/components/health/health-check-item.tsx
@@ -0,0 +1,162 @@
+import { ChevronRight, Copy, Terminal, Wrench } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
+import { useFixHealth, type HealthCheck } from '@/hooks/use-health';
+import { cn } from '@/lib/utils';
+import { useState } from 'react';
+import { toast } from 'sonner';
+
+const statusConfig = {
+ ok: { dot: 'bg-green-500', label: 'OK', labelColor: 'text-green-500' },
+ warning: { dot: 'bg-yellow-500', label: 'WARN', labelColor: 'text-yellow-500' },
+ error: { dot: 'bg-red-500', label: 'ERR', labelColor: 'text-red-500' },
+ info: { dot: 'bg-blue-500', label: 'INFO', labelColor: 'text-blue-500' },
+};
+
+export function HealthCheckItem({ check }: { check: HealthCheck }) {
+ const fixMutation = useFixHealth();
+ const config = statusConfig[check.status];
+ const [isOpen, setIsOpen] = useState(false);
+ const hasExpandableContent = check.details || check.fix;
+
+ const copyToClipboard = (text: string) => {
+ navigator.clipboard.writeText(text);
+ toast.success('Copied to clipboard');
+ };
+
+ // Compact single-line display for items without expandable content
+ if (!hasExpandableContent) {
+ return (
+
+ {/* Status dot with pulse animation */}
+
+
+ {check.status !== 'ok' && (
+
+ )}
+
+
+ {/* Check name */}
+
{check.name}
+
+ {/* Status label */}
+
+ [{config.label}]
+
+
+ {/* Fix button for fixable non-ok items */}
+ {check.fixable && check.status !== 'ok' && (
+
+ )}
+
+ );
+ }
+
+ // Expandable display for items with details or fix commands
+ return (
+
+
+
+
+
+
+
+
+ {/* Message */}
+
{check.message}
+
+ {/* Details block */}
+ {check.details && (
+
+ {check.details}
+
+ )}
+
+ {/* Fix command block */}
+ {check.fix && (
+
+
+
+ {check.fix}
+
+
+
+ {check.fixable && check.status !== 'ok' && (
+
+ )}
+
+ )}
+
+
+
+
+ );
+}
diff --git a/ui/src/components/health/health-gauge.tsx b/ui/src/components/health/health-gauge.tsx
new file mode 100644
index 00000000..2936b726
--- /dev/null
+++ b/ui/src/components/health/health-gauge.tsx
@@ -0,0 +1,91 @@
+import { cn } from '@/lib/utils';
+
+interface HealthGaugeProps {
+ passed: number;
+ total: number;
+ status: 'ok' | 'warning' | 'error';
+ size?: 'sm' | 'md' | 'lg';
+}
+
+const sizeConfig = {
+ sm: { dimension: 80, strokeWidth: 6, fontSize: 'text-lg', labelSize: 'text-[10px]' },
+ md: { dimension: 120, strokeWidth: 8, fontSize: 'text-3xl', labelSize: 'text-xs' },
+ lg: { dimension: 160, strokeWidth: 10, fontSize: 'text-4xl', labelSize: 'text-sm' },
+};
+
+const statusColors = {
+ ok: { stroke: '#22C55E', glow: 'rgba(34, 197, 94, 0.4)' },
+ warning: { stroke: '#EAB308', glow: 'rgba(234, 179, 8, 0.4)' },
+ error: { stroke: '#EF4444', glow: 'rgba(239, 68, 68, 0.4)' },
+};
+
+export function HealthGauge({ passed, total, status, size = 'md' }: HealthGaugeProps) {
+ const config = sizeConfig[size];
+ const colors = statusColors[status];
+ const percentage = total > 0 ? Math.round((passed / total) * 100) : 0;
+
+ const radius = (config.dimension - config.strokeWidth) / 2;
+ const circumference = 2 * Math.PI * radius;
+ const strokeDashoffset = circumference - (percentage / 100) * circumference;
+ const center = config.dimension / 2;
+
+ return (
+
+
+ {/* Center content */}
+
+
+ {percentage}
+
+
+ health
+
+
+
+ );
+}
diff --git a/ui/src/components/health/health-group-section.tsx b/ui/src/components/health/health-group-section.tsx
new file mode 100644
index 00000000..1f7f1740
--- /dev/null
+++ b/ui/src/components/health/health-group-section.tsx
@@ -0,0 +1,111 @@
+import { ChevronDown, Monitor, Settings, Users, Shield, Zap } from 'lucide-react';
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
+import { HealthCheckItem } from './health-check-item';
+import { type HealthGroup } from '@/hooks/use-health';
+import { cn } from '@/lib/utils';
+import { useState } from 'react';
+
+const groupIcons: Record = {
+ Monitor,
+ Settings,
+ Users,
+ Shield,
+ Zap,
+};
+
+interface HealthGroupSectionProps {
+ group: HealthGroup;
+ defaultOpen?: boolean;
+}
+
+export function HealthGroupSection({ group, defaultOpen = true }: HealthGroupSectionProps) {
+ const [isOpen, setIsOpen] = useState(defaultOpen);
+ const Icon = groupIcons[group.icon] || Monitor;
+
+ const passed = group.checks.filter((c) => c.status === 'ok').length;
+ const total = group.checks.length;
+ const hasErrors = group.checks.some((c) => c.status === 'error');
+ const hasWarnings = group.checks.some((c) => c.status === 'warning');
+ const percentage = Math.round((passed / total) * 100);
+
+ // Determine status color
+ const statusColor = hasErrors
+ ? 'text-red-500'
+ : hasWarnings
+ ? 'text-yellow-500'
+ : 'text-green-500';
+ const progressColor = hasErrors ? 'bg-red-500' : hasWarnings ? 'bg-yellow-500' : 'bg-green-500';
+
+ return (
+
+
+ {/* Group header */}
+
+
+
+
+ {/* Checks list */}
+
+
+ {group.checks.map((check) => (
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/ui/src/components/health/health-stats-bar.tsx b/ui/src/components/health/health-stats-bar.tsx
new file mode 100644
index 00000000..042a3f89
--- /dev/null
+++ b/ui/src/components/health/health-stats-bar.tsx
@@ -0,0 +1,85 @@
+import { cn } from '@/lib/utils';
+
+interface HealthStatsBarProps {
+ total: number;
+ passed: number;
+ warnings: number;
+ errors: number;
+ info: number;
+}
+
+interface StatItemProps {
+ label: string;
+ value: number;
+ color: string;
+ bgColor: string;
+}
+
+function StatItem({ label, value, color, bgColor }: StatItemProps) {
+ return (
+
+
+
+ {label}
+
+
{value}
+
+ );
+}
+
+export function HealthStatsBar({ total, passed, warnings, errors, info }: HealthStatsBarProps) {
+ // Calculate percentages for the progress bar
+ const passedPct = (passed / total) * 100;
+ const warningPct = (warnings / total) * 100;
+ const errorPct = (errors / total) * 100;
+ const infoPct = (info / total) * 100;
+
+ return (
+
+ {/* Progress bar visualization */}
+
+ {errorPct > 0 && (
+
+ )}
+ {warningPct > 0 && (
+
+ )}
+ {infoPct > 0 && (
+
+ )}
+ {passedPct > 0 && (
+
+ )}
+
+
+ {/* Stats row */}
+
+
+
+ Checks
+
+ {total}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/ui/src/components/health/index.ts b/ui/src/components/health/index.ts
new file mode 100644
index 00000000..16eac15e
--- /dev/null
+++ b/ui/src/components/health/index.ts
@@ -0,0 +1,9 @@
+/**
+ * Health Components Barrel Export
+ */
+
+export { HealthCard } from './health-card';
+export { HealthCheckItem } from './health-check-item';
+export { HealthGauge } from './health-gauge';
+export { HealthGroupSection } from './health-group-section';
+export { HealthStatsBar } from './health-stats-bar';