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

Certificate Gauges

Agent & Job Gauges

Counters

)} {/* 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}
              
)}
); }