// CIL[BEGIN chunk_id="SignalsPage_module_header_1785000141" type="module_header" identity="module_header" parent="none"] // CIL[DOC-MACRO purpose="Render the Workbench Signals observability page with range filtering, a signals-over-time chart, and a paginated expandable signal list"] // CIL[DOC-RESPONSIBILITY responsibility="Fetch workbench_signal records, filter by selected time range, display a time-series chart, and paginate/expand the signal list"] // CIL[DOC-CONTEXT module="workbench.pages.observe.SignalsPage" layer="frontend-page" related_files="[\"custom/apps/workbench/components/Pagination.tsx\",\"custom/apps/workbench/components/MetricChart.tsx\"]"] // CIL[DOC-CONTRACT inputs="none" outputs="JSX Signals page" errors="fetch errors rendered inline" side_effects="calls /api/admin-data"] // CIL[DOC-MICRO logic_summary="Provides a time range selector, fetches workbench_signal records, buckets counts by hour or day, and renders a chart, summary cards, and a paginated list" notable_decisions=["Buckets signals by startOfHour for 1h/24h ranges and startOfDay for 7d/30d","Expands a row to show the raw record data"]] // 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 { Siren, Signal, AlertTriangle, CheckCircle, ChevronDown, ChevronRight } from 'lucide-react' import { format, parseISO, startOfHour, startOfDay } from 'date-fns' // CIL[END chunk_id="SignalsPage_module_header_1785000141"] // CIL[BEGIN chunk_id="SignalsPage_signalspage_1785000141" type="function" identity="SignalsPage" parent="SignalsPage_module_header_1785000141"] // CIL[DOC-MACRO purpose="Signals observability page implementation"] // CIL[DOC-RESPONSIBILITY responsibility="Display signals filtered by time range with a chart and paginated list"] // CIL[DOC-CONTEXT module="workbench.pages.observe.SignalsPage" layer="frontend-page" related_files="[\"custom/apps/workbench/index.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 signals, filters to the selected range, computes chart data and severity counts, and renders a chart, cards, and a paginated expandable list" notable_decisions=[]] // 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), to: now, } } function bucketSignal(signal: any, range: string) { const created = signal.created_at ? parseISO(signal.created_at) : new Date() return range === '7d' || range === '30d' ? startOfDay(created).toISOString() : startOfHour(created).toISOString() } export default function SignalsPage() { const [range, setRange] = useState('24h') const [signals, setSignals] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(10) const [expanded, setExpanded] = useState(null) useEffect(() => { setLoading(true) setError(null) setPage(1) apiFetch('/api/admin-data?entity=items&type_slug=workbench_signal&limit=1000') .then((r) => r.json()) .then((json) => setSignals(Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : [])) .catch((e) => setError(e.message || 'Failed to load signals')) .finally(() => setLoading(false)) }, [range]) const { from, to } = useMemo(() => rangeDates(range), [range]) const filteredSignals = useMemo(() => { return signals.filter((s) => { const created = s.created_at ? parseISO(s.created_at) : null return created && created >= from && created <= to }) }, [signals, from, to]) const chartData = useMemo(() => { const buckets: Record = {} filteredSignals.forEach((s) => { const key = bucketSignal(s, range) buckets[key] = (buckets[key] || 0) + 1 }) return Object.entries(buckets) .map(([bucket, value]) => ({ bucket, value })) .sort((a, b) => new Date(a.bucket).getTime() - new Date(b.bucket).getTime()) }, [filteredSignals, range]) const activeSignals = filteredSignals.filter((s) => s.status === 'open' || s.status === 'active') const resolvedSignals = filteredSignals.filter((s) => s.status === 'resolved' || s.status === 'closed') const errorSignals = filteredSignals.filter((s) => s.status === 'error' || s.status === 'critical') const pagedSignals = useMemo(() => { const start = (page - 1) * pageSize return filteredSignals.slice(start, start + pageSize) }, [filteredSignals, page, pageSize]) function signalBadge(status: string) { if (status === 'error' || status === 'critical') { return {status} } if (status === 'open' || status === 'active') { return {status} } return {status || 'ok'} } return (

Signals

Operational signals and alerts over time

setRange(e.target.value)} className="w-40"> {RANGE_OPTIONS.map((o) => ( ))}
{error && (
{error}
)} {loading && !signals.length && (
)}
Signals
{formatNumber(filteredSignals.length)}
Active
{formatNumber(activeSignals.length)}
Critical
{formatNumber(errorSignals.length)}
Resolved
{formatNumber(resolvedSignals.length)}
formatNumber(Number(v))} /> Signal List {pagedSignals.length === 0 ? (

No signals in the selected range.

) : ( <>
Signal Type Source Status Created {pagedSignals.map((s) => ( setExpanded(expanded === s.id ? null : s.id)} > {expanded === s.id ? ( ) : ( )} {s.title || 'Untitled'} {s.data?.signal_type || '—'} {s.data?.source_app || '—'} {signalBadge(s.status)} {formatDateTime(s.created_at)} {expanded === s.id && (
{JSON.stringify(s, null, 2)}
)}
))}
)}
) } // CIL[END chunk_id="SignalsPage_signalspage_1785000141"]