Understand your network in real time — a clean, human-readable dashboard that answers "What is happening in my network right now?"
GBTPulse translates raw DNS traffic into plain English — no log files, no noise. You see per-device activity like:
| Device | Activity |
|---|---|
| Markus-PC | GitHub, StackOverflow, YouTube |
| Smart TV | Netflix, AWS CloudFront |
| iPhone | Apple, iCloud, Instagram |
The system is split into four strict layers:
| Layer | Role |
|---|---|
| 1 · Data Acquisition | Poll AdGuard Home & Fritzbox — no logic |
| 2 · Identity Resolution | Map IPs/MACs → human device names (MAC priority) |
| 3 · Domain Normalization | Strip CDN noise, map to service names (Netflix, YouTube…) |
| 4 · Aggregation | Sliding-window QPS + top-domain state per client |
- Docker & Docker Compose
- AdGuard Home running on your network
- Fritzbox router (optional — improves device name resolution)
# Copy and edit environment
cp .env.example .env # or set env vars directly
docker compose up -dOpen http://localhost:5173 for the dashboard.
Backend API: http://localhost:8000
| Variable | Default | Description |
|---|---|---|
ADGUARD_URL |
http://localhost:3000 |
AdGuard Home base URL |
ADGUARD_USERNAME |
admin |
AdGuard credentials |
ADGUARD_PASSWORD |
admin |
AdGuard credentials |
FRITZBOX_ADDRESS |
http://fritz.box |
Fritzbox address |
FRITZBOX_USERNAME |
(empty) | Fritzbox user (optional) |
FRITZBOX_PASSWORD |
(empty) | Fritzbox password |
POLL_ADGUARD_INTERVAL |
3 |
Seconds between AdGuard polls |
POLL_FRITZBOX_INTERVAL |
15 |
Seconds between Fritzbox polls |
SLIDING_WINDOW_SECONDS |
60 |
Aggregation window size |
cd backend
pip install -r requirements.txt
python main.py # runs on :8000Run tests:
cd backend
pytest tests/ -vcd frontend
npm install
cp .env.example .env # set VITE_WS_URL if backend is not on localhost:8000
npm run dev # runs on :5173gbtpulse/
├── backend/
│ ├── main.py # FastAPI app + background tasks
│ ├── config.py # Environment configuration
│ ├── acquisition/
│ │ ├── adguard.py # AdGuard Home polling
│ │ └── fritzbox.py # Fritzbox TR-064 polling
│ ├── identity/
│ │ └── resolver.py # IP/MAC → device name resolution
│ ├── normalization/
│ │ └── domains.py # Domain → service name mapping
│ ├── aggregation/
│ │ └── state.py # Sliding-window state store
│ └── tests/ # 20 pytest tests
├── frontend/
│ └── src/
│ ├── App.tsx
│ ├── components/
│ │ ├── ClientCard.tsx # Per-device activity card
│ │ ├── StatusBar.tsx # Connection + global QPS
│ │ ├── TrafficFlow.tsx # Grid of client cards
│ │ └── QpsChart.tsx # Live QPS bar chart
│ └── hooks/
│ └── useWebSocket.ts # Auto-reconnecting WebSocket
└── docker-compose.yml
Connect to ws://<host>:8000/ws — receives a snapshot every second:
{
"timestamp": "2026-04-18T13:27:08Z",
"global_qps": 3.42,
"clients": [
{
"client_id": "aa:bb:cc:dd:ee:ff",
"name": "Markus-PC",
"ip": "192.168.178.22",
"top_domains": ["GitHub", "Google", "YouTube"],
"recent_domains": ["GitHub", "StackOverflow", "npm"],
"qps": 1.83,
"query_count": 110,
"last_seen": "2026-04-18T13:27:07Z"
}
]
}REST fallback: GET /api/snapshot
