TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import Link from "next/link";2import type { Metadata } from "next";3import { ENTITY_TYPES } from "@websensor/core/client";4import { Chip, Empty, PageHeader, Panel, Score, Table, Tabs, Td } from "@/components/ui";5import { api } from "@/lib/api";6import { fmtInt, fmtScore, relTime, typeLabel } from "@/lib/format";78export const dynamic = "force-dynamic";9export const metadata: Metadata = { title: "Entities", description: "Organizations, products, models, APIs and other entities that events resolve to — ranked by importance, activity, velocity, quality and confirmation.", alternates: { canonical: "/entities" } };1011type Search = { tab?: string; type?: string; q?: string };1213export default async function EntitiesPage({ searchParams }: { searchParams: Promise<Search> }) {14 const sp = await searchParams;15 const tab = sp.tab === "all" || sp.type || sp.q ? "all" : "ranked";16 return (17 <>18 <PageHeader compact title="Entities" description="Everything important resolves to an entity. Each entity has a timeline — the equivalent of git history for its public Web presence." />19 <Tabs20 className="mb-3"21 current={tab}22 items={[23 { key: "ranked", label: "Ranked", href: "/entities" },24 { key: "all", label: "All", href: "/entities?tab=all" },25 ]}26 />27 {tab === "ranked" ? <Ranked /> : <All sp={sp} />}28 </>29 );30}3132async function Ranked() {33 const { items } = await api.rank(200);34 return (35 <Panel36 title={<>WebSensor rank <span className="font-mono text-fg-subtle">{fmtInt(items.length)}</span></>}37 dense38 action={<span className="hidden text-[11px] text-fg-subtle md:inline">importance × activity × velocity × quality × confirmation — not raw counts</span>}39 >40 <p className="border-b border-line px-3 py-2 text-[12px] text-fg-muted md:hidden">Ranking uses importance × activity × velocity × quality × confirmation, not raw counts.</p>41 {items.length === 0 ? (42 <Empty>The ranking is computed from the last 7 days of activity — nothing to rank yet.</Empty>43 ) : (44 <Table head={["#", "Entity", "Type", "Rank", "24 h", "7 d", "Sources", "Avg signal", "Confirmed", "Silent 24 h", "Breaking 24 h", "Last"]}>45 {items.map((e) => (46 <tr key={e.id} className="hover:bg-panel-2/60">47 <Td mono className="text-fg-subtle">{e.rank}</Td>48 <Td>49 <Link href={`/entity/${e.id}`} className="font-medium hover:underline">{e.name}</Link>50 {e.domain && <div className="truncate font-mono text-[10.5px] text-fg-subtle">{e.domain}</div>}51 </Td>52 <Td><Chip href={`/entities?tab=all&type=${e.type}`}>{typeLabel(e.type)}</Chip></Td>53 <Td><Score value={e.rank_score} size="sm" title={`Rank score ${fmtScore(e.rank_score)} · entity importance ${fmtScore(e.importance)}`} /></Td>54 <Td mono className={e.events_24h ? "" : "text-fg-subtle"}>{fmtInt(e.events_24h)}</Td>55 <Td mono className="text-fg-muted">{fmtInt(e.events_7d)}</Td>56 <Td mono>{fmtInt(e.sources)}</Td>57 <Td mono>{fmtScore(e.avg_signal)}</Td>58 <Td mono className={e.confirmed_ratio > 0 ? "text-ok" : "text-fg-subtle"}>{Math.round(e.confirmed_ratio * 100)}%</Td>59 <Td mono className={e.silent_24h ? "text-silent" : "text-fg-subtle"}>{fmtInt(e.silent_24h)}</Td>60 <Td mono className={e.breaking_24h ? "text-hot" : "text-fg-subtle"}>{fmtInt(e.breaking_24h)}</Td>61 <Td mono className="whitespace-nowrap text-fg-subtle">{relTime(e.last_at)}</Td>62 </tr>63 ))}64 </Table>65 )}66 </Panel>67 );68}6970async function All({ sp }: { sp: Search }) {71 const { items } = await api.entities({ type: sp.type, q: sp.q, limit: 400 });72 const types = new Set(items.map((e) => e.type));73 return (74 <>75 <form className="mb-3 flex flex-wrap items-center gap-2" action="/entities">76 <input type="hidden" name="tab" value="all" />77 <input name="q" defaultValue={sp.q ?? ""} placeholder="Search entities…" className="h-8 w-64 max-w-full rounded-md border border-line bg-panel px-2.5 text-[13px] placeholder:text-fg-subtle" />78 {sp.type && <input type="hidden" name="type" value={sp.type} />}79 <button type="submit" className="h-8 rounded-md border border-line bg-panel-2 px-3 text-[12.5px]">Search</button>80 <span className="font-mono text-[11px] text-fg-subtle tabular">{fmtInt(items.length)} entit{items.length === 1 ? "y" : "ies"}</span>81 </form>82 <div className="mb-3 flex flex-wrap gap-1">83 <Chip href={sp.q ? `/entities?tab=all&q=${encodeURIComponent(sp.q)}` : "/entities?tab=all"} tone={!sp.type ? "signal" : "default"}>all</Chip>84 {ENTITY_TYPES.filter((t) => types.has(t) || t === sp.type).map((t) => (85 <Chip key={t} href={`/entities?tab=all&type=${t}${sp.q ? `&q=${encodeURIComponent(sp.q)}` : ""}`} tone={sp.type === t ? "signal" : "default"}>{typeLabel(t)}</Chip>86 ))}87 </div>88 <Panel dense>89 {items.length === 0 ? (90 <Empty>No entities match.</Empty>91 ) : (92 <Table head={["Entity", "Type", "Domain", "Importance", "Events", "24 h", "Last event"]}>93 {items.map((e) => (94 <tr key={e.id} className="hover:bg-panel-2/60">95 <Td><Link href={`/entity/${e.id}`} className="font-medium hover:underline">{e.name}</Link></Td>96 <Td><Chip>{typeLabel(e.type)}</Chip></Td>97 <Td mono className="text-fg-subtle">{e.domain ?? "—"}</Td>98 <Td mono>{fmtScore(e.importance)}</Td>99 <Td mono>{fmtInt(e.event_count)}</Td>100 <Td mono className={e.events_24h ? "text-signal" : "text-fg-subtle"}>{e.events_24h ?? 0}</Td>101 <Td mono className="whitespace-nowrap text-fg-subtle">{e.last_event_at ? relTime(e.last_event_at) : "—"}</Td>102 </tr>103 ))}104 </Table>105 )}106 </Panel>107 </>108 );109}110