SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
7.4 KB · 116 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { EventRow } from "@/components/event-row";4import { BreakingRail } from "@/components/rail";5import { Empty, PageHeader, Panel, Sparkline, Stat } from "@/components/ui";6import { api } from "@/lib/api";7import { fmtInt, GROUP_LABELS, relTime, CHANNELS } from "@/lib/format";89export const dynamic = "force-dynamic";10export const metadata: Metadata = { title: "Pulse", description: "What is changing on the Internet right now: global activity, breaking events, fastest-rising entities, cyber incidents, AI developments, market and government events, infrastructure incidents, silent changes." };1112/** Real-time summary of the Internet (spec §39). */13export default async function PulsePage() {14  const p = await api.pulse();15  if (!p) return <Empty>Pulse is warming up.</Empty>;16  const activity = p.activity ?? [];17  const evSeries = activity.map((a) => Number(a.events));18  const chSeries = activity.map((a) => Number(a.changes));19  const groups = Object.entries(p.by_group_24h ?? {}).sort((a, b) => b[1] - a[1]);20  const maxG = Math.max(1, ...groups.map(([, n]) => n));21  const deskLabel = (d: string): string => CHANNELS.find((c) => c.key === d)?.label ?? d;22  return (23    <>24      <PageHeader compact kicker="Right now" title="Pulse" description="A live summary of what is changing on the public web — generated from real observations, refreshed every ten seconds." />25      <div className="panel mb-4 grid grid-cols-2 divide-x divide-y divide-line sm:grid-cols-3 lg:grid-cols-6 lg:divide-y-0">26        <Stat label="Events · 1 h" value={fmtInt(p.totals?.events_1h)} hint="meaningful signals" tone="signal" />27        <Stat label="Raw changes · 1 h" value={fmtInt(p.totals?.changes_1h)} hint="before noise filtering" />28        <Stat label="Checks · 5 min" value={fmtInt(p.totals?.checks_5m)} hint={`${Math.round(Number(p.totals?.checks_5m ?? 0) / 5)}/min`} />29        <Stat label="Active sources · 24 h" value={fmtInt(p.totals?.active_sources_24h)} />30        <Stat label="Countries · 24 h" value={fmtInt(p.totals?.active_countries_24h)} />31        <Stat label="Breaking / developing" value={`${p.breaking.filter((c) => c.state === "breaking").length} / ${p.breaking.filter((c) => c.state === "developing").length}`} tone="hot" />32      </div>33      <div className="grid grid-cols-1 gap-4 xl:grid-cols-[1fr_340px] [&>*]:min-w-0">34        <div className="flex flex-col gap-4">35          <Panel title="Global activity · last 6 h · 15-minute buckets">36            <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 [&>*]:min-w-0">37              <div>38                <div className="label mb-1">Events</div>39                <Sparkline values={evSeries} width={420} height={56} tone="signal" responsive />40              </div>41              <div>42                <div className="label mb-1">Raw changes</div>43                <Sparkline values={chSeries} width={420} height={56} tone="muted" responsive />44              </div>45            </div>46            <p className="mt-2 text-[11px] text-fg-subtle">The gap between raw changes and events is the noise WebSensor filtered out: timestamps, navigation churn, cookie banners, advertising, cosmetic edits.</p>47          </Panel>48          <div className="grid gap-4 md:grid-cols-2">49            {(p.desks ?? []).filter((d) => d.items.length).map((d) => (50              <Panel key={d.desk} title={`${deskLabel(d.desk)} · 12 h`} dense action={<Link href={`/category/${d.desk}`} className="text-[11px] text-fg-subtle hover:text-fg">desk →</Link>}>51                {d.items.map((e) => (52                  <EventRow key={e.id} ev={e} />53                ))}54              </Panel>55            ))}56          </div>57          <Panel title="Infrastructure incidents · 6 h" dense action={<Link href="/live?group=reliability" className="text-[11px] text-fg-subtle hover:text-fg">live →</Link>}>58            {p.infrastructure.length ? p.infrastructure.map((e) => <EventRow key={e.id} ev={e} />) : <Empty>No outage, incident or maintenance signal in the last 6 h.</Empty>}59          </Panel>60          <Panel title="Silent changes · 24 h" dense action={<Link href="/silent" className="text-[11px] text-fg-subtle hover:text-fg">all →</Link>}>61            {p.silent_changes.length ? p.silent_changes.map((e) => <EventRow key={e.id} ev={e} />) : <Empty>No silent change in the last 24 h.</Empty>}62          </Panel>63        </div>64        <aside className="flex flex-col gap-4">65          <BreakingRail items={p.breaking.filter((c) => c.state === "breaking")} developing={p.breaking.filter((c) => c.state === "developing")} />66          <Panel title="Fastest-rising entities · 3 h" dense>67            {p.rising_entities.length ? (68              <ol className="divide-y divide-line">69                {p.rising_entities.map((r, i) => (70                  <li key={r.id} className="grid grid-cols-[1.25rem_1fr_auto] items-center gap-2 px-3 py-1.5 text-[13px]">71                    <span className="font-mono text-[11px] text-fg-subtle tabular">{i + 1}</span>72                    <div className="min-w-0">73                      <Link href={`/entity/${r.id}`} className="block truncate font-medium hover:underline">{r.name}</Link>74                      <div className="text-[11px] text-fg-subtle">{r.events_3h} signals in 3 h · {r.events_prev_24h} in the prior 24 h</div>75                    </div>76                    <span className="font-mono text-[12px] font-semibold text-signal tabular">×{r.acceleration}</span>77                  </li>78                ))}79              </ol>80            ) : (81              <Empty>No entity is accelerating right now.</Empty>82            )}83          </Panel>84          <Panel title="Anomalous sources · 2 h" dense>85            {p.anomalies.length ? (86              <ul className="divide-y divide-line">87                {p.anomalies.map((s) => (88                  <li key={s.id} className="flex items-center gap-2 px-3 py-1.5 text-[13px]">89                    <Link href={`/source/${s.id}`} className="min-w-0 flex-1 truncate hover:underline">{s.name}</Link>90                    <span className="text-[11px] text-fg-subtle">{s.changes_2h} vs {s.baseline_per_day}/d</span>91                    <span className={`font-mono text-[12px] font-semibold tabular ${s.activity_score >= 70 ? "text-hot" : "text-high"}`}>{s.pct_vs_baseline !== null && s.pct_vs_baseline !== undefined ? `+${Math.min(9999, s.pct_vs_baseline)}%` : Math.round(s.activity_score)}</span>92                  </li>93                ))}94              </ul>95            ) : (96              <Empty>All sources within baseline.</Empty>97            )}98          </Panel>99          <Panel title="Events by group · 24 h">100            <ul className="space-y-1.5">101              {groups.map(([g, n]) => (102                <li key={g} className="grid grid-cols-[8rem_1fr_3rem] items-center gap-2 text-[12.5px]">103                  <Link href={`/live?group=${g}`} className="truncate hover:underline">{GROUP_LABELS[g] ?? g}</Link>104                  <div className="h-1 w-full overflow-hidden rounded-full bg-panel-2"><div className="h-full rounded-full bg-info" style={{ width: `${(n / maxG) * 100}%` }} /></div>105                  <span className="text-right font-mono text-fg-subtle tabular">{fmtInt(n)}</span>106                </li>107              ))}108            </ul>109          </Panel>110          <p className="text-[11px] text-fg-subtle">Generated {relTime(p.generated_at)} · all values come from stored observations; nothing is estimated.</p>111        </aside>112      </div>113    </>114  );115}116