diff --git a/ui/src/pages/health.tsx b/ui/src/pages/health.tsx
index 5239fd5c..211de300 100644
--- a/ui/src/pages/health.tsx
+++ b/ui/src/pages/health.tsx
@@ -1,15 +1,31 @@
-import { Button } from '@/components/ui/button';
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ Activity,
+ AlertTriangle,
+ CheckCircle2,
+ Copy,
+ Cpu,
+ Info,
+ RefreshCw,
+ Terminal,
+} from 'lucide-react';
+import { toast } from 'sonner';
import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
-import { RefreshCw, Terminal, Copy, Cpu } from 'lucide-react';
import { HealthGauge } from '@/components/health/health-gauge';
-import { HealthStatsBar } from '@/components/health/health-stats-bar';
import { HealthGroupSection } from '@/components/health/health-group-section';
import { useHealth, type HealthGroup } from '@/hooks/use-health';
import { cn } from '@/lib/utils';
-import { toast } from 'sonner';
-import { useEffect, useState } from 'react';
-import { useTranslation } from 'react-i18next';
+import { PageShell, PageHeader } from '@/components/page-shell';
+import {
+ MonitorLayout,
+ KpiRow,
+ KpiCard,
+ MonitorGrid,
+ MonitorCard,
+} from '@/components/monitor-layout';
function getOverallStatus(summary: { passed: number; warnings: number; errors: number }) {
if (summary.errors > 0) return 'error';
@@ -41,57 +57,16 @@ function sortGroupsByIssues(groups: HealthGroup[]): HealthGroup[] {
});
}
-function TerminalHeader() {
- return (
-
- $
- ccs doctor
-
- );
-}
-
-function LoadingSkeleton() {
- return (
-
- {/* Hero skeleton */}
-
-
- {/* Stats skeleton */}
-
-
- {/* Groups skeleton */}
-
- {[1, 2, 3, 4].map((i) => (
-
- ))}
-
-
- );
-}
-
export function HealthPage() {
const { t } = useTranslation();
const { data, isLoading, refetch, dataUpdatedAt } = useHealth();
- // Use dataUpdatedAt directly instead of storing in state
- const lastRefresh = dataUpdatedAt;
-
- // Update relative time display by forcing re-render every second
+ // Force re-render every second so the relative-time label stays fresh.
const [tick, setTick] = useState(0);
useEffect(() => {
- const interval = setInterval(() => setTick((t) => t + 1), 1000);
+ const interval = setInterval(() => setTick((n) => n + 1), 1000);
return () => clearInterval(interval);
}, []);
- // Consume tick to prevent unused variable warning
void tick;
const copyDoctorCommand = () => {
@@ -105,94 +80,46 @@ export function HealthPage() {
};
if (isLoading && !data) {
- return ;
+ return ;
}
const overallStatus = data ? getOverallStatus(data.summary) : 'ok';
const sortedGroups = data?.groups ? sortGroupsByIssues(data.groups) : [];
return (
-
- {/* Hero Section - Terminal-inspired control center header */}
-
- {/* Subtle scan lines effect */}
-
-
- {/* Grid pattern background */}
-
-
-
- {/* Left: Health Gauge - excludes info from percentage */}
- {data && (
-
-
-
- )}
-
- {/* Center: Title and status */}
-
- {/* Terminal prompt */}
-
-
- {/* Main title */}
-
-
- {t('health.systemHealth')}
-
- {data?.version && (
-
- {t('health.build', { version: data.version })}
-
- )}
-
-
- {/* Status message */}
-
-
- {t('health.lastScan')}
-
- {lastRefresh ? formatRelativeTime(lastRefresh, t) : '--'}
-
- |
- {t('health.autoRefresh')}
- 30s
-
-
-
- {/* Right: Actions */}
-
+
+
+ {t('health.systemHealth')}
+ {data?.version && (
+
+ {t('health.build', { version: data.version })}
+
+ )}
+
+ }
+ description={
+
+
+ {t('health.lastScan')}
+ {dataUpdatedAt ? formatRelativeTime(dataUpdatedAt, t) : '--'}
+ |
+ {t('health.autoRefresh')}
+ 30s
+
+ }
+ actions={
+ <>
+ >
+ }
+ />
+
+
+ }
+ />
+ }
+ />
+ 0 ? 'warning' : 'default'}
+ icon={}
+ />
+ 0 ? 'negative' : 'default'}
+ icon={}
+ />
+
+ )
+ }
+ >
+
+ {/* Hero: overall health gauge with terminal aesthetic */}
+ {data && (
+
+ $
+ ccs doctor
+
+ }
+ meta={data.summary.info > 0 ? `${data.summary.info} info` : undefined}
+ >
+
+
+
+
Status
+
+ {overallStatus === 'ok' && 'All systems nominal'}
+ {overallStatus === 'warning' && 'Attention required'}
+ {overallStatus === 'error' && 'Errors detected'}
+
+
+ {data.summary.passed}/{data.summary.total - data.summary.info} non-informational
+ checks passing
+
+
+
+
+ )}
+
+ {/* Each health group becomes its own MonitorCard */}
+ {sortedGroups.map((group, index) => (
+
+ c.status === 'error' || c.status === 'warning')
+ }
+ />
+
+ ))}
+
+
+ {/* Footer */}
+
+
+
+ {t('health.version')} {data?.version ?? '--'}
+
+
+ {t('health.platform')}{' '}
+
+ {typeof navigator !== 'undefined' ? navigator.platform : 'linux'}
+
+
+
+
+
+
{t('health.liveMonitoring')}
-
+
+
+ );
+}
- {/* Stats Bar */}
- {data && (
-
-
-
- )}
-
- {/* Health Check Groups - Single column layout */}
- {sortedGroups.length > 0 && (
-
- {sortedGroups.map((group, index) => (
-
c.status === 'error' || c.status === 'warning')
- }
- />
+function HealthLoadingSkeleton() {
+ return (
+
+ } />
+
+ {[1, 2, 3, 4].map((i) => (
+
+ ))}
+
+ }
+ >
+
+
+
+
+ {[1, 2, 3].map((i) => (
+
+
+
))}
-
- )}
-
- {/* Footer metadata */}
-
-
-
- {t('health.version')} {data?.version ?? '--'}
-
-
- {t('health.platform')}{' '}
-
- {typeof navigator !== 'undefined' ? navigator.platform : 'linux'}
-
-
-
-
-
-
{t('health.liveMonitoring')}
-
-
-
+
+
+
);
}