Real-time cyber-attack visualization on a 3D globe.
NetFlare pulls malicious IPs from AbuseIPDB, resolves them to coordinates with MaxMind GeoLite2, and streams them to a React client that plots each hit on an interactive globe. Global attack trends come from the Cloudflare Radar API.
Attacks are cached in SQLite and drip-fed over a WebSocket, so the globe stays live without hammering the upstream APIs.
- Live globe — attack arcs and impact points rendered with
react-globe.gl - WebSocket stream — new events pushed to every connected client
- Trends panel — Cloudflare Radar layer-7 timeseries, top origin and target countries
- Event ticker — scrolling feed of recent IPs with click-to-focus on the globe
- Threat audio — ambient severity cues driven by the current threat level
- Persistent cache — SQLite store survives restarts; upstream APIs polled on a schedule
- Watch demo on YouTube by Clicking Below
---
config:
layout: elk
---
flowchart TB
subgraph EXT["🌐 External Data Sources"]
A1["AbuseIPDB API<br>(blacklist endpoint)<br>malicious IPs + confidence scores"]
A2["Cloudflare Radar API<br>attack trends + traffic spikes"]
A3["GeoLite2 DB (MaxMind)<br>local IP → lat/lon lookup"]
end
subgraph BE["⚙️ Backend — FastAPI"]
B1["Scheduler (APScheduler)<br>polls APIs every 5 min"]
B2["Ingestion Service<br>httpx async fetchers"]
B3["ML Classifier<br>Random Forest / XGBoost<br>DDoS likelihood score"]
B4["Geolocation Service<br>geoip2 lookup"]
B5[("Cache / Store<br>Redis or SQLite")]
B6["REST endpoint<br>GET /attacks"]
B7["WebSocket endpoint<br>/ws — live event push"]
end
subgraph FE["🖥️ Frontend"]
C1["Globe.gl (Three.js)<br>3D globe with arcs & pulses"]
C2["Trends Panel<br>charts from Radar data"]
C3["WebSocket client<br>receives live events"]
end
A1 -- "IP list + abuse scores" --> B2
A2 -- "attack trend data" --> B2
B1 -- "triggers" --> B2
B2 -- "raw IP records" --> B3
B3 -- "high-confidence DDoS IPs" --> B4
A3 -. "offline lookup" .-> B4
B4 -- "(lat, lon, score, country)" --> B5
B5 --> B6
B5 -- "new events" --> B7
B6 -- "initial load (JSON)" --> C1
B7 -- "live updates" --> C3
C3 -- "spawn arcs + ripples" --> C1
B6 -- "trend stats" --> C2
style EXT fill:none,stroke:#4A5568,stroke-width:2px,stroke-dasharray: 4 4,color:#2D3748
style BE fill:none,stroke:#4A5568,stroke-width:2px,stroke-dasharray: 4 4,color:#2D3748
style FE fill:none,stroke:#4A5568,stroke-width:2px,stroke-dasharray: 4 4,color:#2D3748
style A1 fill:#FF6B6B,stroke:#C53030,stroke-width:2px,color:#000000
style A2 fill:#FF8E53,stroke:#C53030,stroke-width:2px,color:#000000
style A3 fill:#FFA8A8,stroke:#C53030,stroke-width:2px,color:#000000
style B1 fill:#4ECDC4,stroke:#2B6CB0,stroke-width:2px,color:#000000
style B2 fill:#45B7D1,stroke:#2B6CB0,stroke-width:2px,color:#000000
style B3 fill:#96CEB4,stroke:#2B6CB0,stroke-width:2px,color:#000000
style B4 fill:#82C91E,stroke:#2B6CB0,stroke-width:2px,color:#000000
style B5 fill:#4DABF7,stroke:#2B6CB0,stroke-width:2px,color:#000000
style B6 fill:#339AF0,stroke:#2B6CB0,stroke-width:2px,color:#000000
style B7 fill:#74C0FC,stroke:#2B6CB0,stroke-width:2px,color:#000000
style C1 fill:#CC5DE8,stroke:#6B46C1,stroke-width:2px,color:#000000
style C2 fill:#B197FC,stroke:#6B46C1,stroke-width:2px,color:#000000
style C3 fill:#DA77F2,stroke:#6B46C1,stroke-width:2px,color:#000000
| Job | Interval | Purpose |
|---|---|---|
refresh_attack |
6 hours | Fetch AbuseIPDB blacklist, geolocate, store |
refresh_trends |
1 hour | Fetch Cloudflare Radar trends |
replay_random_attack |
3 seconds | Queue a stored attack to keep the feed live |
- Python 3.11+
- Node.js 18+
- An AbuseIPDB API key
- A Cloudflare API token with Radar read access
- The GeoLite2 City database
cd server
pip install -r requirements.txt # or: uv syncPlace GeoLite2-City.mmdb in server/data/, then create server/.env:
ABUSEIPDB_KEY=your_key_here
CLOUDFLARE_TOKEN=your_token_here
ALLOWED_ORIGINS=http://localhost:5173Run it:
uvicorn main:app --reloadThe API is live at http://localhost:8000.
cd client
npm install
npm run devThe app is live at http://localhost:5173.
To point the client at a deployed backend, create client/.env:
VITE_API_URL=https://your-server.onrender.com
VITE_WS_URL=wss://your-server.onrender.com/ws| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Health check |
GET |
/attacks |
200 most recent attacks (lat, lng, score, country) |
GET |
/trends |
Cached Cloudflare Radar trend data |
GET |
/debug/fake |
Queue a synthetic event (development only) |
WS |
/ws |
Live attack event stream |
| Variable | Side | Default | Description |
|---|---|---|---|
ABUSEIPDB_KEY |
server | required | AbuseIPDB API key |
CLOUDFLARE_TOKEN |
server | required | Cloudflare Radar API token |
ALLOWED_ORIGINS |
server | http://localhost:5173 |
Comma-separated CORS origins |
PORT |
server | 8000 |
Listen port |
VITE_API_URL |
client | http://localhost:8000 |
REST base URL |
VITE_WS_URL |
client | ws://localhost:8000/ws |
WebSocket URL |
The server ships with a render.yaml and a Procfile for Render —
set ABUSEIPDB_KEY, CLOUDFLARE_TOKEN, and ALLOWED_ORIGINS in the dashboard.
The client is a static Vite build (npm run build) and deploys to any static host
(Vercel, Netlify, Cloudflare Pages).
Note: SQLite lives on the container filesystem. On ephemeral hosts the cache resets on redeploy and repopulates on first boot.
NetFlare/
├── client/
│ ├── public/ # logo, icons, favicon
│ └── src/
│ ├── components/ # GlobeView, Ticker, TrendsPanel, StatusBar, Legend, BootOverlay
│ ├── hooks/ # useAttacks, useLiveEvents, useTrends
│ └── audio/ # threat-level audio cues
├── server/
│ ├── main.py # FastAPI app, scheduler, WebSocket
│ ├── db.py # SQLite schema, upserts, trend storage
│ ├── radar.py # Cloudflare Radar client
│ ├── connection_manager.py
│ └── data/ # GeoLite2 DB + SQLite cache
└── render.yaml
- AbuseIPDB — malicious IP reports
- Cloudflare Radar — global attack trends
- MaxMind GeoLite2 — IP geolocation
- react-globe.gl — globe rendering
