TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import Link from "next/link";2import type { Metadata } from "next";3import { Badge, Chip, Empty, Flag, PageHeader, Panel, Table, Td, TierBadge } from "@/components/ui";4import { api } from "@/lib/api";5import { fmtInt, relTime } from "@/lib/format";67export const dynamic = "force-dynamic";8export const metadata: Metadata = { title: "Sources", description: "Every organization monitored by WebSensor, with its sensors and recent activity. Filter by country, tier, category and provenance." };910const CATS = ["ai", "cloud", "developer", "cyber", "consumer-tech", "semiconductors", "finance", "government", "statistics", "health", "pharma", "science", "space", "automotive", "commerce", "payments", "crypto", "enterprise", "internet", "standards", "news", "media", "sports", "transport", "energy"];11const TIERS = ["S", "A", "B", "C", "D"];1213type Params = { q?: string; category?: string; country?: string; tier?: string; first_party?: string };1415function href(p: Params, patch: Partial<Params>): string {16 const n: Record<string, string | undefined> = { ...p, ...patch };17 const sp = new URLSearchParams();18 for (const k of ["q", "category", "country", "tier", "first_party"]) if (n[k]) sp.set(k, n[k]!);19 const s = sp.toString();20 return s ? `/sources?${s}` : "/sources";21}2223export default async function SourcesPage({ searchParams }: { searchParams: Promise<Params> }) {24 const raw = await searchParams;25 const sp: Params = { q: raw.q?.trim() || undefined, category: raw.category || undefined, country: raw.country?.toUpperCase() || undefined, tier: TIERS.includes((raw.tier ?? "").toUpperCase()) ? raw.tier!.toUpperCase() : undefined, first_party: raw.first_party === "true" || raw.first_party === "false" ? raw.first_party : undefined };26 const [{ items }, countries] = await Promise.all([api.sources({ q: sp.q, category: sp.category, country: sp.country, tier: sp.tier, first_party: sp.first_party }), api.countries()]);27 const active = [sp.q, sp.category, sp.country, sp.tier, sp.first_party].filter(Boolean).length;28 const firstParty = items.filter((s) => s.first_party !== false).length;29 return (30 <>31 <PageHeader compact kicker={`${fmtInt(items.length)} organizations · ${fmtInt(firstParty)} first-party · ${fmtInt(items.length - firstParty)} media`} title="Sources" description="WebSensor monitors sensors, not merely domains: each organization exposes several official endpoints (feeds, status pages, sitemaps, pricing and documentation pages). Media sources are marked EXTERNAL and never count as first-party evidence." />32 <form className="mb-2 flex flex-wrap items-center gap-2" action="/sources">33 <input name="q" defaultValue={sp.q ?? ""} placeholder="Filter by name or domain…" aria-label="Filter by name or domain" className="h-8 w-full max-w-64 min-w-0 rounded-md border border-line bg-panel px-2.5 text-[13px] placeholder:text-fg-subtle sm:w-64" />34 <select name="country" defaultValue={sp.country ?? ""} aria-label="Country" className="h-8 max-w-[14rem] rounded-md border border-line bg-panel px-2 text-[12.5px]">35 <option value="">all countries</option>36 {countries.items.map((c) => (37 <option key={c.country} value={c.country}>{c.name} · {c.sources}</option>38 ))}39 </select>40 {sp.category && <input type="hidden" name="category" value={sp.category} />}41 {sp.tier && <input type="hidden" name="tier" value={sp.tier} />}42 {sp.first_party && <input type="hidden" name="first_party" value={sp.first_party} />}43 <button type="submit" className="h-8 rounded-md border border-line bg-panel-2 px-3 text-[12.5px] hover:border-line-strong">Filter</button>44 {active > 0 && <Link href="/sources" className="text-[12px] text-fg-subtle hover:text-fg">clear {active} filter{active === 1 ? "" : "s"}</Link>}45 </form>46 <div className="mb-2 flex flex-wrap items-center gap-1">47 <span className="label mr-1">tier</span>48 <Chip href={href(sp, { tier: undefined })} tone={!sp.tier ? "signal" : "default"} className="font-mono">all</Chip>49 {TIERS.map((t) => (50 <Chip key={t} href={href(sp, { tier: t })} tone={sp.tier === t ? "signal" : "default"} className="font-mono">{t}</Chip>51 ))}52 <span className="label mx-1">·</span>53 <Chip href={href(sp, { first_party: undefined })} tone={!sp.first_party ? "signal" : "default"}>all kinds</Chip>54 <Chip href={href(sp, { first_party: "true" })} tone={sp.first_party === "true" ? "signal" : "default"}>first-party</Chip>55 <Chip href={href(sp, { first_party: "false" })} tone={sp.first_party === "false" ? "signal" : "default"}>media / external</Chip>56 </div>57 <div className="mb-3 flex flex-wrap items-center gap-1">58 <span className="label mr-1">category</span>59 <Chip href={href(sp, { category: undefined })} tone={!sp.category ? "signal" : "default"}>all</Chip>60 {CATS.map((c) => (61 <Chip key={c} href={href(sp, { category: c })} tone={sp.category === c ? "signal" : "default"}>{c}</Chip>62 ))}63 {sp.category && !CATS.includes(sp.category) && <Chip tone="signal">{sp.category}</Chip>}64 </div>65 <Panel dense>66 {items.length === 0 ? (67 <Empty>No source matches these filters.</Empty>68 ) : (69 <Table head={["Tier", "Source", "Domain", "Country", "Kind", "Categories", "Sensors", "Events 24 h", "Total", "Last event", "Health"]}>70 {items.map((s) => (71 <tr key={s.id} className="hover:bg-panel-2/60">72 <Td><TierBadge tier={s.tier} /></Td>73 <Td><Link href={`/source/${s.id}`} className="font-medium hover:underline">{s.name}</Link></Td>74 <Td mono><Link href={`/domain/${s.domain}`} className="text-fg-muted hover:underline">{s.domain}</Link></Td>75 <Td>{s.country ? <Link href={href(sp, { country: s.country })} className="inline-flex items-center gap-1 font-mono text-[11px] text-fg-muted hover:text-fg"><Flag code={s.country} /> {s.country}</Link> : <span className="text-fg-subtle">—</span>}</Td>76 <Td>{s.first_party === false ? <Badge kind="external" compact /> : <Badge kind="first-party" compact />}</Td>77 <Td><div className="flex flex-wrap gap-1">{s.categories.slice(0, 3).map((c) => <Chip key={c} href={href(sp, { category: c })}>{c}</Chip>)}{s.categories.length > 3 && <span className="font-mono text-[10.5px] text-fg-subtle">+{s.categories.length - 3}</span>}</div></Td>78 <Td mono>{s.sensor_count ?? 0}</Td>79 <Td mono>{fmtInt(s.events_24h ?? 0)}</Td>80 <Td mono className="text-fg-subtle">{fmtInt(s.event_count)}</Td>81 <Td mono className="whitespace-nowrap text-fg-subtle">{s.last_event_at ? relTime(s.last_event_at) : "—"}</Td>82 <Td>{(s.sensors_degraded ?? 0) > 0 ? <Chip tone="warn" className="font-mono">{s.sensors_degraded} DEGRADED</Chip> : <Chip tone="ok" className="font-mono">UP</Chip>}</Td>83 </tr>84 ))}85 </Table>86 )}87 </Panel>88 </>89 );90}91