import { useQuery } from '@tanstack/react-query';
import { getMetrics, getPrometheusMetrics, getHealth } from '../api/client';
import PageHeader from '../components/PageHeader';
import ErrorState from '../components/ErrorState';
function MetricCard({ label, value, sub }: { label: string; value: string | number; sub?: string }) {
return (
{label}
{value}
{sub &&
{sub}
}
);
}
function formatUptime(seconds: number): string {
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
if (d > 0) return `${d}d ${h}h ${m}m`;
if (h > 0) return `${h}h ${m}m`;
return `${m}m`;
}
export default function ObservabilityPage() {
const { data: metrics, isLoading, error, refetch } = useQuery({
queryKey: ['metrics'],
queryFn: getMetrics,
refetchInterval: 15000,
});
const { data: health } = useQuery({
queryKey: ['health'],
queryFn: getHealth,
refetchInterval: 15000,
});
const { data: promText } = useQuery({
queryKey: ['prometheus-metrics'],
queryFn: getPrometheusMetrics,
refetchInterval: 30000,
retry: false,
});
if (error) {
return (
<>
refetch()} />
>
);
}
return (
<>
{/* Health status */}
Server {health?.status === 'ok' ? 'Healthy' : 'Unhealthy'}
{metrics && (
Uptime: {formatUptime(metrics.uptime.uptime_seconds)} | Started: {new Date(metrics.uptime.server_started).toLocaleString()}
)}
{/* Gauge metrics */}
{isLoading && (
Loading metrics...
)}
{metrics && (
<>
>
)}
{/* Prometheus config */}
Prometheus Integration
Add this scrape target to your prometheus.yml:
{`scrape_configs:
- job_name: 'certctl'
metrics_path: '/api/v1/metrics/prometheus'
scheme: 'https'
bearer_token: ''
static_configs:
- targets: [':443']`}
{/* Live Prometheus output */}
{promText && (
Live Prometheus Output
GET /api/v1/metrics/prometheus
text/plain
{promText}
)}
>
);
}