SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
3.5 KB · 63 lines tsx
Raw Blame History
1'use client';23import { useState } from 'react';4import { SeriesChart } from '@/components/charts/SeriesChart';5import { fmt, fmtInt } from '@/lib/format';6import type { AdminBaselines } from '@/lib/types';7import { AdminPage, ErrorNote, Field, Panel, inputCls, useAdmin } from './shared';89const SIGNALS = ['ttfb_z', 'tcp_z', 'rtt_z', 'loss', 'dns_fail_rate', 'dns_latency_z', 'resolver_disagreement', 'target_down_corroborated', 'fail_rate_z', 'http_5xx_rate', 'tls_fail_rate', 'reset_timeout_rate', 'route_change_rate', 'hop_count_z', 'path_latency_shift', 'bgp_withdrawals_z', 'bgp_announcements_z', 'bgp_origin_changes_z', 'bgp_collector_disagreement', 'vendor_incidents'];1011export function Baselines() {12  const [signal, setSignal] = useState('ttfb_z');13  const [scopeType, setScopeType] = useState('global');14  const [scopeId, setScopeId] = useState('');15  const { data, err, loading } = useAdmin<AdminBaselines>('/baselines', { signal_id: signal, scope_type: scopeType, scope_id: scopeId || undefined });16  const pts = data?.points ?? [];17  return (18    <AdminPage title="Baseline diagnostics" desc="Value vs rolling median ± MAD for one signal and scope. If the median drifts with the value, the baseline window is too short or the exclusion too small.">19      <div className="grid grid-cols-2 gap-3 md:grid-cols-4">20        <Field label="signal_id">21          <select value={signal} onChange={(e) => setSignal(e.target.value)} className={`${inputCls} num w-full`}>22            {SIGNALS.map((s) => (23              <option key={s}>{s}</option>24            ))}25          </select>26        </Field>27        <Field label="scope_type">28          <select value={scopeType} onChange={(e) => setScopeType(e.target.value)} className={`${inputCls} w-full`}>29            {['global', 'region', 'country', 'asn', 'service', 'probe', 'target'].map((s) => (30              <option key={s}>{s}</option>31            ))}32          </select>33        </Field>34        <Field label="scope_id">35          <input value={scopeId} onChange={(e) => setScopeId(e.target.value)} placeholder="na-east · CA · 13335 · …" className={`${inputCls} num w-full`} />36        </Field>37        <div className="num flex items-end text-[11px] text-ink-3">{data ? `${fmtInt(data.samples)} samples · ${data.baseline_days} d baseline` : loading ? 'loading…' : ''}</div>38      </div>39      <ErrorNote err={err} />40      {pts.length > 0 && (41        <Panel className="mt-3">42          <SeriesChart43            lines={[44              { name: 'value', color: '#E6EDF3', points: pts.map((p) => ({ ts: p.ts, value: p.value })), width: 1.25 },45              { name: 'median', color: '#5B8DEF', points: pts.map((p) => ({ ts: p.ts, value: p.median })), dashed: true },46              { name: 'median + MAD', color: '#8B98A5', points: pts.map((p) => ({ ts: p.ts, value: p.median + p.mad })), dashed: true, width: 0.8 },47              { name: 'median − MAD', color: '#8B98A5', points: pts.map((p) => ({ ts: p.ts, value: p.median - p.mad })), dashed: true, width: 0.8 },48              { name: 'robust z', color: '#E76F51', points: pts.map((p) => ({ ts: p.ts, value: p.z })), yAxisIndex: 1 },49            ]}50            yMax="auto"51            bands={false}52            height={300}53            y2={{ max: 'auto', name: 'z' }}54          />55          <p className="num mt-2 text-[11px] text-ink-3">56            latest: value {fmt(pts.at(-1)!.value)} · median {fmt(pts.at(-1)!.median)} · MAD {fmt(pts.at(-1)!.mad, 2)} · z {fmt(pts.at(-1)!.z)}57          </p>58        </Panel>59      )}60    </AdminPage>61  );62}63