/** * The client column of the query tables reads as a name wherever one is known, * with the same precedence the Clients page applies: a hand-typed `name` wins, * the reverse-DNS `learned_name` stands in muted behind it, and an address with * neither — including one the loaded list has never seen — stays bare. * * The muted colour is the whole of the affordance here. The Clients page pairs * it with an outlined "learned" tag, and keeps it: one mention per client is * information. Repeating that tag down every row of a query table is noise, so * the tables carry the name alone. */ import { useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import * as stylex from "@stylexjs/stylex"; import { clientsQuery } from "@/lib/queries"; import type { Client } from "@/lib/types"; import { styles as shared } from "@/ui/styles"; export type ClientNames = ReadonlyMap>; /** * The live stream names clients the loaded list has never seen. Polling folds * them in on the next tick, which keeps the lookup a single cached query * instead of a fetch fired per unknown address. */ const CLIENTS_POLL_MS = 30_000; export function useClientNames(): ClientNames { const { data } = useQuery({ ...clientsQuery(), refetchInterval: CLIENTS_POLL_MS }); return useMemo( () => new Map( (data ?? []).map((client) => [client.ip, { name: client.name, learned_name: client.learned_name }]), ), [data], ); } export function ClientName({ ip, names }: { ip: string; names: ClientNames }) { const client = names.get(ip); if (client === undefined || (client.name === "" && client.learned_name === "")) { return {ip}; } // The name replaces the address on screen, so the address stays reachable // as the tooltip rather than disappearing from the row entirely. if (client.name !== "") return {client.name}; return ( {client.learned_name} ); }