spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1'use client';23import { useState } from 'react';4import { SeriesChart } from '@/components/charts/SeriesChart';5import { adminFetch } from '@/lib/admin-fetch';6import { fmt } from '@/lib/format';7import { COMPONENT_LABEL, COMPONENT_ORDER } from '@/lib/pressure';8import type { AdminConfig, AdminReplay } from '@/lib/types';9import { AdminPage, ErrorNote, Field, Panel, btnPrimary, inputCls, useAdmin } from './shared';1011export function Replay() {12 const { data: cfg } = useAdmin<AdminConfig>('/config');13 const [from, setFrom] = useState(() => new Date(Date.now() - 86400_000).toISOString().slice(0, 16));14 const [to, setTo] = useState(() => new Date().toISOString().slice(0, 16));15 const [weights, setWeights] = useState<Record<string, number> | null>(null);16 const [res, setRes] = useState<AdminReplay | null>(null);17 const [err, setErr] = useState<string | null>(null);18 const [busy, setBusy] = useState(false);19 const w = weights ?? cfg?.pressure_weights ?? Object.fromEntries(COMPONENT_ORDER.map((c) => [c, 0]));20 const sum = Object.values(w).reduce((a, b) => a + Number(b || 0), 0);21 const valid = Math.abs(sum - 1) <= 0.001;2223 const run = async () => {24 setBusy(true);25 setErr(null);26 try {27 setRes(await adminFetch<AdminReplay>('/replay', { method: 'POST', body: { from: new Date(from + 'Z').toISOString(), to: new Date(to + 'Z').toISOString(), weights: w } }));28 } catch (e) {29 setErr(String(e));30 } finally {31 setBusy(false);32 }33 };3435 const diffs = res ? res.points.map((p) => p.pressure_replayed - p.pressure_original) : [];36 const maxDiff = diffs.length ? Math.max(...diffs.map(Math.abs)) : 0;3738 return (39 <AdminPage title="Replay" desc="Recompute the index over a past window with alternative weights, from stored signal features. Validates scoring changes against real incidents before applying them.">40 <div className="grid grid-cols-[minmax(0,1fr)] gap-3 lg:grid-cols-[320px_minmax(0,1fr)]">41 <Panel title="Window & weights" right={<span className="num" style={{ color: valid ? 'var(--ok)' : 'var(--bad)' }}>Σ {fmt(sum, 3)}</span>}>42 <div className="grid grid-cols-2 gap-3">43 <Field label="from (UTC)">44 <input type="datetime-local" value={from} onChange={(e) => setFrom(e.target.value)} className={`${inputCls} num w-full`} />45 </Field>46 <Field label="to (UTC)">47 <input type="datetime-local" value={to} onChange={(e) => setTo(e.target.value)} className={`${inputCls} num w-full`} />48 </Field>49 </div>50 <ul className="mt-3 space-y-2">51 {Object.entries(w).map(([id, v]) => (52 <li key={id} className="grid grid-cols-[minmax(0,1fr)_80px] items-center gap-3 text-[12.5px]">53 <span className="text-ink">{COMPONENT_LABEL[id as keyof typeof COMPONENT_LABEL] ?? id}</span>54 <input type="number" step="0.01" min={0} max={1} value={v} onChange={(e) => setWeights({ ...w, [id]: Number(e.target.value) })} className={`${inputCls} num w-full`} aria-label={`${id} weight`} />55 </li>56 ))}57 </ul>58 <button type="button" className={`${btnPrimary} mt-3 w-full`} disabled={!valid || busy} onClick={() => void run()}>59 {busy ? 'replaying…' : 'Replay'}60 </button>61 <ErrorNote err={err} />62 </Panel>63 <Panel title="Original vs replayed" right={res ? <span className="num">step {res.step_seconds} s · max |Δ| {fmt(maxDiff)}</span> : undefined}>64 {res ? (65 <SeriesChart66 lines={[67 { name: 'original', color: '#8B98A5', points: res.points.map((p) => ({ ts: p.ts, value: p.pressure_original })), area: true },68 { name: 'replayed', color: '#5B8DEF', points: res.points.map((p) => ({ ts: p.ts, value: p.pressure_replayed })), width: 1.5 },69 ]}70 height={340}71 />72 ) : (73 <p className="py-10 text-center text-[12px] text-ink-3">Choose a window and weights, then replay.</p>74 )}75 </Panel>76 </div>77 </AdminPage>78 );79}80