Attack origins
Inbound requests converge on a protected site at the centre of a dotted globe. Blocked ones dent its shield ring and burst off it.
Your data
Pass your requests as events, or call push() on the ref for a live feed. Without events the component shows a seeded demo stream with invented origins and prefilled demo counts. See Your data.
Playground
Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.
<GeoThreats maxInFlight={24} windowSeconds={60} shieldRadius={26} live />Installation
pnpm dlx shadcn@latest add @lumesec/geo-threatsFirst timeRegister the @lumesec registry once, or install by URL+
Adds @lumesec to your components.json:
pnpm dlx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.jsonOr skip the setup and install by URL:
npx shadcn@latest add https://elements.lumesec.ai/r/geo-threats.jsonUsage
import { GeoThreats, type ThreatEvent } from "@/components/lumesec/geo-threats";
export function Example({ requests }: { requests: ThreatEvent[] }) {
return <GeoThreats target={{ label: "eu-central", lat: 50.11, lon: 8.68 }} events={requests} />;
}Behaviour
Inbound requests from around the world converge on one protected site at the centre of a globe; requests from the far side come up over the horizon. A ring of dots around the site is its shield: blocked requests hit the ring and burst off it, allowed requests pass through and land.
Where a request hits, the shield dents and lights up for a moment. Counts per verdict and the top origin regions roll beside the globe; arcs in flight are capped and the rest are folded into the counts.
API reference
Props
Also accepts every prop of <div> (React.ComponentProps<"div">), spread onto the root element.
targetTypeThreatTargetDefault{ label: "eu-central", lat: 50.1109, lon: 8.6821 }{ label, lat, lon } of the protected site. The globe faces it, so it sits at the centre of the disc; the label shows in the header and the accessible summary.
eventsTypereadonly ThreatEvent[]Requests: { id, lat, lon, verdict, region?, time? } with verdict blocked or allowed. Each new id animates once, so the array can grow or be replaced. Without it a seeded demo stream runs; pass an empty array to show only what you push().
maxInFlightTypenumberDefault24Arcs drawn at once. Further requests go straight into the counts.
windowSecondsTypenumberDefault60Window for the counts and the origin list, in seconds. Requests older than the window are ignored.
shieldRadiusTypenumberDefault26Radius of the shield ring in px, from 10 to 120 and at most 60% of the globe's radius.
liveTypebooleanDefaulttrueRuns the demo stream while events is not given. Off keeps the prefilled demo counts without new requests.
onEventType(event: ThreatEvent) => voidCalled when a request reaches the shield (blocked) or the site (allowed). Requests without an arc call it at once.
refTypeReact.Ref<GeoThreatsHandle>Exposes push().
Ref
ref receives a GeoThreatsHandle handle with these methods.
push(event: ThreatEvent): voidAdds one request, with or without events. An id that was already seen is ignored.
Accessibility
- The globe stage has
role="img"and anaria-labelthat summarises the window, for example "Requests to eu-central. In the last minute: 1,284 blocked, 312 allowed, most from Southern Asia." It is refreshed at most every 15 seconds. - The counters and the origin list are real text. Each origin row has a visually hidden sentence with its region, total, blocked and allowed counts; the rolling digits and the dotted bars are
aria-hidden. - Both canvases, the server placeholder and the target square are
aria-hidden. The globe takes no pointer or keyboard input. - Reduced motion: no arcs, dents or sparks play: each request lights the shield dots facing its origin for 400 ms and is counted at once, the counts swap without rolling and the origin rows move without sliding.
Theming
Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from --lumesec. See Theming.
This component reads --lumesec, --lumesec-glint, --lumesec-shine, --lumesec-success, --destructive, --foreground, --muted-foreground, --card and --border.
Notes
- The globe always faces the target and never turns, so each arc is projected once. Arcs rise higher the farther they travel; origins on the far side climb over the horizon, where a short glint marks the arc.
- Packets speed up toward the site: 1 s plus up to 0.8 s for the far side of the world. A blocked packet stops at the ring: the dots around the impact dent inward on springs, heat up and throw ten sparks back the way it came. An allowed packet parts the ring, passes and lands with a small ring of lit dots around the site.
- Requests are grouped by
region. Without it, the group is the world region of the origin (such asSouthern AsiaorSouth America) from the embedded country data; points at sea take the nearest country's region. - Counts cover the last
windowSeconds, by each request'stime(or when it arrived). The origin list shows the five regions with the most requests and re-sorts at most once a second; each row has a dotted bar with the region's share of all requests, blocked dots first. - Requests older than 10 seconds, requests that arrive while the globe is off screen or the tab is hidden, and requests beyond
maxInFlightare counted without an arc. Ids are remembered (the last 4,000), so a re-render with the same array never counts them twice. - The stage is square, at most 420 px wide; at 560 px of container width and above the origin list stands beside it, else below. The arcs and sparks draw on a canvas that reaches 40 px past the stage on every side.
- The server markup shows the disc with a 30° graticule, the shield ring and the target; it fades to the dotted globe after the first frame. The animation loop pauses while the globe is off screen and stops when nothing moves.
- The demo stream sends about four requests a second, 80% of them blocked, from invented sources around real cities, and prefills a window of counts on mount.
Related
Was this page helpful?