// CIL[BEGIN chunk_id="UptimePage_module_header_1785000041" type="module_header" identity="module_header" parent="none"] // CIL[DOC-MACRO purpose="Render the Workbench Uptime observability page"] // CIL[DOC-RESPONSIBILITY responsibility="Show availability, request volume, error rate, and recent outage traces"] // CIL[DOC-CONTEXT module="workbench.pages.observe.UptimePage" layer="frontend-page" related_files="[\"custom/apps/workbench/functions/custom_workbench-observability.ts\",\"custom/apps/workbench/components/MetricChart.tsx\"]"] // CIL[DOC-CONTRACT inputs="none" outputs="JSX Uptime page" errors="fetch errors rendered inline" side_effects="calls /api/custom_workbench-observability"] // CIL[DOC-MICRO logic_summary="Provides a time range selector, fetches an observability summary and trace stats, computes uptime percentage, and renders charts and an outage table" notable_decisions=["Uptime is derived as 100% minus the error rate series","Overall uptime uses log total/error counts"]] // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"] // CIL[DOC-STABILITY stability="experimental"] // CIL[DOC-AUDIENCE audience="developer"] import { useEffect, useMemo, useState, Fragment } from 'react' import { apiFetch } from '@core/lib/api' import { formatDateTime, formatNumber } from '@core/lib/utils' import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card' import { NativeSelect } from '@core/components/ui/native-select' import { LoadingSpinner } from '@core/components/ui/LoadingSpinner' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@core/components/ui/table' import { MetricChart } from '../../components/MetricChart' import { Pagination } from '../../components/Pagination' import { TraceTree } from '../../components/TraceTree' import { Clock, Activity, AlertTriangle, TrendingUp, ChevronDown, ChevronRight } from 'lucide-react' // CIL[END chunk_id="UptimePage_module_header_1785000041"] // CIL[BEGIN chunk_id="UptimePage_uptimePage_1785000041" type="function" identity="UptimePage" parent="UptimePage_module_header_1785000041"] // CIL[DOC-MACRO purpose="Uptime observability page implementation"] // CIL[DOC-RESPONSIBILITY responsibility="Display availability metrics and recent outages"] // CIL[DOC-CONTEXT module="workbench.pages.observe.UptimePage" layer="frontend-page" related_files="[\"custom/apps/workbench/pages/observe/ServicesPage.tsx\"]"] // CIL[DOC-CONTRACT inputs="none" outputs="JSX" errors="fetch errors rendered inline" side_effects="network requests on mount and range change"] // CIL[DOC-MICRO logic_summary="Fetches summary and trace stats for the selected range, computes uptime percentage from logs, and renders charts and an outage table" notable_decisions=["Outages are traces with error_count > 0"]] // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"] // CIL[DOC-STABILITY stability="experimental"] // CIL[DOC-AUDIENCE audience="developer"] const RANGE_OPTIONS = [ { label: 'Last hour', value: '1h', ms: 60 * 60 * 1000 }, { label: 'Last 24 hours', value: '24h', ms: 24 * 60 * 60 * 1000 }, { label: 'Last 7 days', value: '7d', ms: 7 * 24 * 60 * 60 * 1000 }, { label: 'Last 30 days', value: '30d', ms: 30 * 24 * 60 * 60 * 1000 }, ] function rangeDates(value: string) { const now = new Date() const option = RANGE_OPTIONS.find((o) => o.value === value) || RANGE_OPTIONS[1] return { from: new Date(now.getTime() - option.ms).toISOString(), to: now.toISOString(), } } function granularityFor(range: string) { return range === '30d' ? '1 day' : '1 hour' } interface SummaryData { date_from: string date_to: string granularity: string logs: { total: number error_count: number } metrics: { request_count: { bucket: string; value: number }[] error_rate: { bucket: string; value: number }[] } } interface TraceRow { trace_id: string root_span_name: string started_at: string duration_ms: number span_count: number error_count: number outcome: string } export default function UptimePage() { const [range, setRange] = useState('24h') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [summary, setSummary] = useState(null) const [traces, setTraces] = useState([]) const [outagePage, setOutagePage] = useState(1) const [outagePageSize, setOutagePageSize] = useState(10) const [outageExpanded, setOutageExpanded] = useState(null) const load = async () => { const { from, to } = rangeDates(range) const granularity = granularityFor(range) setLoading(true) setError(null) try { const base = `/api/custom_workbench-observability?date_from=${encodeURIComponent(from)}&date_to=${encodeURIComponent(to)}&granularity=${encodeURIComponent(granularity)}` const [sumRes, traceRes] = await Promise.all([ apiFetch(`${base}&action=summary`), apiFetch(`${base}&action=traces`), ]) const [sumJson, traceJson] = await Promise.all([sumRes.json(), traceRes.json()]) if (sumJson.error) throw new Error(sumJson.error) if (traceJson.error) throw new Error(traceJson.error) setSummary(sumJson.data || null) const tracePayload = traceJson.data || { data: [], total: 0 } setTraces((tracePayload.data || []).map((r: any) => ({ ...r, duration_ms: Number(r.duration_ms || 0), span_count: Number(r.span_count || 0), error_count: Number(r.error_count || 0), }))) } catch (e: any) { setError(e.message || 'Failed to load uptime data') } finally { setLoading(false) } } useEffect(() => { load() setOutagePage(1) }, [range]) const requestData = useMemo( () => (summary?.metrics?.request_count || []).map((r: any) => ({ bucket: r.bucket, value: Number(r.value) })), [summary] ) const errorData = useMemo( () => (summary?.metrics?.error_rate || []).map((r: any) => ({ bucket: r.bucket, value: Number(r.value) })), [summary] ) const uptimeData = useMemo( () => errorData.map((row) => ({ bucket: row.bucket, value: Math.max(0, (1 - row.value) * 100), })), [errorData] ) const logs = summary?.logs || { total: 0, error_count: 0 } const totalRequests = logs.total || 0 const totalErrors = logs.error_count || 0 const uptimePercent = totalRequests > 0 ? ((totalRequests - totalErrors) / totalRequests) * 100 : 100 const allOutageTraces = useMemo( () => traces .filter((t) => t.error_count > 0 || t.outcome === 'failure') .sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()), [traces] ) const outageTraces = useMemo(() => { const start = (outagePage - 1) * outagePageSize return allOutageTraces.slice(start, start + outagePageSize) }, [allOutageTraces, outagePage, outagePageSize]) return (

Uptime

Availability, error rate, and recent outages

setRange(e.target.value)} className="w-40"> {RANGE_OPTIONS.map((o) => ( ))}
{error && (
{error}
)} {loading && !summary && (
)} {summary && ( <>
Uptime
{uptimePercent.toFixed(2)}%
Total Requests
{formatNumber(totalRequests)}
Failed Requests
{formatNumber(totalErrors)}
Outage Traces
{formatNumber(allOutageTraces.length)}
`${Number(v).toFixed(1)}%`} yTickFormatter={(v) => `${Number(v).toFixed(0)}%`} /> formatNumber(Number(v))} /> `${(Number(v) * 100).toFixed(1)}%`} yTickFormatter={(v) => `${(Number(v) * 100).toFixed(0)}%`} />
Recent Outages {outageTraces.length === 0 ? (

No outage traces in the selected range.

) : ( <>
Trace Root Span Started Duration Spans Errors {outageTraces.map((row) => ( setOutageExpanded(outageExpanded === row.trace_id ? null : row.trace_id)} > {outageExpanded === row.trace_id ? ( ) : ( )} {row.trace_id?.slice(0, 8)}… {row.root_span_name || '—'} {formatDateTime(row.started_at)} {row.duration_ms.toFixed(0)} ms {formatNumber(row.span_count)} {formatNumber(row.error_count)} {outageExpanded === row.trace_id && ( )} ))}
)}
)}
) } // CIL[END chunk_id="UptimePage_uptimePage_1785000041"]