- Backend: Add score_type query parameter to filter detections by threat level (BOT, REGLE, BOT_REGLE, SCORE) - Frontend: Add score_type dropdown filter in DetectionsList component - Frontend: Add IP detection route redirect (/detections/ip/:ip → /investigation/:ip) - Frontend: Add DetectionAttributesSection component showing variability metrics - API client: Update detectionsApi to support score_type parameter Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
🛡️ Bot Detector Dashboard
Dashboard web interactif pour visualiser et investiguer les décisions de classification du Bot Detector IA.
Version: 2.0.0 - TCP Fingerprinting Multi-Signal + Clustering IPs Multi-Métriques
🚀 Démarrage Rapide
Prérequis
- Docker et Docker Compose
- Le service
clickhousedéjà déployé - Des données dans la table
ml_detected_anomalies - Des données dans la table
http_logs(pour les user-agents)
Note: Le dashboard peut fonctionner indépendamment de
bot_detector_ai. Il lit les données déjà détectées dans ClickHouse.
Lancement
# 1. Vérifier que .env existe
cp .env.example .env # Si ce n'est pas déjà fait
# 2. Lancer le dashboard (avec Docker Compose v2)
docker compose up -d dashboard_web
# Ou avec l'ancienne syntaxe
docker-compose up -d dashboard_web
# 3. Ouvrir le dashboard
# http://localhost:3000
Arrêt
docker compose stop dashboard_web
Vérifier le statut
# Voir les services en cours d'exécution
docker compose ps
# Voir les logs en temps réel
docker compose logs -f dashboard_web
📊 Fonctionnalités
Dashboard Principal
- Métriques en temps réel : Total détections, menaces, bots connus, IPs uniques
- Comparaison baseline J-1 : variation ▲▼ vs hier (détections, IPs uniques, CRITICAL)
- Répartition par menace : Visualisation CRITICAL/HIGH/MEDIUM/LOW
- Évolution temporelle : Graphique des détections sur 24h
- Incidents clusterisés : Regroupement automatique par subnet /24
- Top Menaces Actives : Top 10 des IPs les plus dangereuses
🧬 TCP Spoofing & Fingerprinting OS (amélioré v2.0)
- Détection multi-signal : TTL initial + MSS + scale + fenêtre TCP (p0f-style)
- 20 signatures OS : Linux, Windows, macOS, Android, iOS, Masscan, ZMap, Shodan, Googlebot…
- Estimation hop-count : différence TTL initial (arrondi) − TTL observé
- Détection réseau : MSS → Ethernet (1460) / PPPoE (1452) / VPN (1420) / Tunnel (<1420)
- Confiance 0–100% : score pondéré (TTL 40% + MSS 30% + fenêtre 20% + scale 10%)
- Badge bot-tool : Masscan détecté à 97% (win=5808, mss=1452, scale=4)
- Distribution MSS : histogramme des MSS observés par cluster
🔬 Clustering IPs Multi-Métriques (nouveau v2.0)
- URL:
/clustering - Algorithme : K-means++ (Arthur & Vassilvitskii, 2007), initialisé avec k-means++, 3 runs
- 21 features normalisées [0,1] :
- Stack TCP : TTL initial, MSS, scale, fenêtre TCP
- Anomalie ML : score, vélocité, fuzzing, headless, POST ratio, IP-ID zéro
- TLS/Protocole : ALPN mismatch, ALPN absent, efficacité H2 (multiplexing)
- Navigateur : score navigateur moderne, ordre headers, UA-CH mismatch
- Temporel : entropie, diversité JA4, UA rotatif
- Positionnement 2D : PCA par puissance itérative (Hotelling) + déflation
- Nommage automatique : Masscan / Bot UA Rotatif / Bot Fuzzer / Anomalie ML / Linux / Windows / VPN
Vue Tableau de bord (défaut) :
- Grille de cartes groupées : Bots confirmés → Suspects → Légitimes
- Chaque carte : label, IP count, hits, badge CRITIQUE/ÉLEVÉ/MODÉRÉ/SAIN
- 4 mini-barres : anomalie, UA-CH mismatch, fuzzing, UA rotatif
- Stack TCP (TTL, MSS, Scale), top pays, ASN
Vue Graphe de relations :
- Nœuds-cartes ReactFlow (220px, texte lisible)
- Colonnes par niveau de menace : Bots | Suspects | Légitimes
- Arêtes colorées par similarité (orange=fort, animé=très fort)
- Légende intégrée, minimap, contrôles zoom
Sidebar de détail :
- RadarChart comportemental (10 axes : anomalie, UA-CH, fuzzing, headless…)
- Toutes les métriques avec barres de progression colorées
- Liste des IPs avec badges menace/pays/ASN
- Export Copier IPs + ⬇ CSV
Investigation Subnet /24
- URL:
/entities/subnet/x.x.x.x_24 - Stats globales, tableau des IPs, actions par IP
Investigation IP + Réputation
- URL:
/investigation/:ip - Synthèse multi-sources (ML + bruteforce + TCP + JA4 + timeline)
- Score de risque 0–100, réputation IP-API + IPinfo
Investigation (Variabilité)
- User-Agents, JA4 fingerprints, pays, ASN, hosts, niveaux de menace
- Insights automatiques, navigation enchaînable
🏗️ Architecture
┌─────────────────────────────────────────────────────────┐
│ Docker Compose │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ ClickHouse │ │ bot_detector│ │ dashboard_web │ │
│ │ :8123 │ │ (existant) │ │ :8000 (web+API)│ │
│ │ :9000 │ │ │ │ network=host │ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ └────────────────┴───────────────────┘ │
└─────────────────────────────────────────────────────────┘
Le container utilise
network_mode: "host"— le frontend buildé est servi par FastAPI sur le port 8000 uniquement (pas de port 3000 en production).
Composants
| Composant | Technologie | Description |
|---|---|---|
| Frontend | React 18 + TypeScript 5 + Vite 5 + Tailwind CSS 3 | Interface utilisateur (SPA) |
| Backend API | FastAPI 0.111 + Python 3.11 | API REST + serveur statique SPA |
| Database | ClickHouse (existant) — port 8123 | Base de données principale |
| Clustering | K-means++ pur Python + PCA puissance itérative | Algorithmes embarqués, sans dépendance ML |
📁 Structure
dashboard/
├── Dockerfile # Multi-stage: node:20-alpine → python:3.11-slim
├── docker-compose.yaml
├── requirements.txt
├── backend/
│ ├── main.py # FastAPI: CORS, routers, SPA catch-all (doit être DERNIER)
│ ├── config.py # pydantic-settings, lit .env
│ ├── database.py # ClickHouseClient singleton (db)
│ ├── models.py # Modèles Pydantic v2
│ ├── routes/
│ │ ├── metrics.py # GET /api/metrics, /api/metrics/baseline
│ │ ├── detections.py # GET /api/detections
│ │ ├── variability.py # GET /api/variability
│ │ ├── attributes.py # GET /api/attributes
│ │ ├── incidents.py # GET /api/incidents/clusters
│ │ ├── entities.py # GET /api/entities
│ │ ├── analysis.py # GET/POST /api/analysis — classifications SOC
│ │ ├── reputation.py # GET /api/reputation/ip/{ip}
│ │ ├── tcp_spoofing.py # GET /api/tcp-spoofing — fingerprinting OS multi-signal
│ │ ├── clustering.py # GET /api/clustering/clusters + /cluster/{id}/ips
│ │ └── investigation_summary.py # GET /api/investigation/{ip}/summary
│ └── services/
│ ├── tcp_fingerprint.py # 20 signatures OS, scoring, hop-count, réseau path
│ ├── clustering_engine.py # K-means++, PCA-2D, nommage, score risque (pur Python)
│ └── reputation_ip.py # httpx → ip-api.com + ipinfo.io (async, sans API key)
└── frontend/
├── package.json
├── vite.config.ts # Proxy /api → :8000 en dev
└── src/
├── App.tsx # BrowserRouter + Sidebar + TopHeader + Routes
├── ThemeContext.tsx # dark/light/auto, localStorage: soc_theme
├── api/client.ts # Axios baseURL=/api + toutes les interfaces TypeScript
├── components/
│ ├── ClusteringView.tsx # K-means++ clustering — 2 vues
│ ├── TcpSpoofingView.tsx # TCP fingerprinting OS
│ ├── InvestigationView.tsx # Investigation IP complète
│ └── ... # Autres vues
├── hooks/ # useMetrics, useDetections, useVariability (polling)
└── utils/STIXExporter.ts
🔌 API
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/metrics |
Métriques globales |
| GET | /api/metrics/baseline |
Comparaison J-1 (détections, IPs, CRITICAL) |
| GET | /api/metrics/threats |
Distribution par menace |
| GET | /api/detections |
Liste des détections paginée |
| GET | /api/detections/{id} |
Détails d'une détection |
| GET | /api/variability/{type}/{value} |
Variabilité d'un attribut |
| GET | /api/attributes/{type} |
Valeurs uniques d'un attribut |
| GET | /api/incidents/clusters |
Incidents clusterisés par subnet /24 |
| GET | /api/entities/subnet/{subnet} |
Investigation subnet (ex: 141.98.11.0_24) |
| GET | /api/entities/{type}/{value} |
Investigation entité (IP, JA4, UA…) |
| GET | /api/reputation/ip/{ip} |
Réputation IP (IP-API + IPinfo) |
| GET | /api/investigation/{ip}/summary |
Synthèse IP multi-sources (ML + TCP + JA4) |
| GET | /api/analysis/{ip}/subnet |
Analyse subnet / ASN |
| GET | /api/analysis/{ip}/recommendation |
Recommandation de classification |
| POST | /api/analysis/classifications |
Sauvegarder classification SOC |
| GET | /api/tcp-spoofing/overview |
Vue d'ensemble TCP spoofing + OS |
| GET | /api/tcp-spoofing/list |
Liste des détections TCP spoofing |
| GET | /api/tcp-spoofing/matrix |
Matrice OS déclaré vs OS réel |
| GET | /api/clustering/clusters |
Clustering K-means++ (?k=14&n_samples=3000) |
| GET | /api/clustering/cluster/{id}/ips |
IPs d'un cluster (drill-down) |
| GET | /health |
Health check |
Exemples
# Health check
curl http://localhost:8000/health
# Métriques globales + baseline
curl http://localhost:8000/api/metrics | jq '.summary'
curl http://localhost:8000/api/metrics/baseline | jq
# Détections CRITICAL
curl "http://localhost:8000/api/detections?threat_level=CRITICAL&page=1" | jq '.items | length'
# TCP Spoofing — vue d'ensemble
curl http://localhost:8000/api/tcp-spoofing/overview | jq
# Clustering IPs (14 clusters sur 3000 échantillons)
curl "http://localhost:8000/api/clustering/clusters?k=14&n_samples=3000" | jq '.stats'
# Drill-down d'un cluster
curl "http://localhost:8000/api/clustering/cluster/c0_k14/ips?limit=20" | jq '.ips[].ip'
# Réputation IP
curl http://localhost:8000/api/reputation/ip/162.55.94.175 | jq
⚙️ Configuration
Variables d'Environnement
| Variable | Défaut | Description |
|---|---|---|
CLICKHOUSE_HOST |
clickhouse |
Hôte ClickHouse |
CLICKHOUSE_PORT |
8123 |
Port HTTP ClickHouse |
CLICKHOUSE_DB |
mabase_prod |
Base de données |
CLICKHOUSE_USER |
admin |
Utilisateur |
CLICKHOUSE_PASSWORD |
`` | Mot de passe |
API_HOST |
0.0.0.0 |
Bind Uvicorn |
API_PORT |
8000 |
Port API + frontend |
CORS_ORIGINS |
["http://localhost:3000", ...] |
Origines CORS autorisées |
Ces variables sont lues depuis le fichier .env à la racine du projet.
⚠️ Le fichier
.envcontient les credentials réels — ne jamais le committer.
🔍 Workflows d'Investigation
Exemple 1 : Identifier un bot Masscan
- 🔬 Clustering IPs → Cluster "🤖 Masscan / Scanner IP" visible en rouge
- Clic sur la carte → Sidebar : TTL=52, MSS=1452, Scale=4 — pattern Masscan
- Copier les IPs → Liste prête pour le blocage
- Export CSV → Import dans le SIEM ou firewall
Exemple 2 : Analyser des bots UA-rotatifs (cloud)
- Clustering → Cluster "🤖 Bot UA Rotatif + CH Mismatch" (risque 50%)
- RadarChart → UA-CH=100%, UA rotatif=100%, anomalie=59%
- Top ASN → Microsoft, Google, Akamai — cloud providers
- 🧬 TCP Spoofing → Confirmer : ces IPs déclarent Windows UA mais ont TTL Linux
- Investigation IP → Détail complet avec timeline 24h
Exemple 3 : Détecter le spoofing d'OS
- 🧬 TCP Spoofing → Liste des IPs avec mismatch OS
- Matrice UA×OS → User-Agent Android mais stack TCP Windows = spoof
- Confiance 85% → MSS=1460 (Ethernet), scale=7, TTL≈64 → Linux réel
- Action → Classer comme bot avec IP proxy
Exemple 4 : Investiguer une IP suspecte
- 🎯 Détections → IP classifiée 🔴 CRITICAL
- Clic sur l'IP → Synthèse : ML + TCP + JA4 + bruteforce + timeline
- Score de risque : 85/100
- User-Agents → 3 UA différents en 24h (rotation)
- TCP → TTL initial 128 (Windows) mais UA Linux → spoof
- Action → Blacklist immédiate
🧬 Services techniques (v2.0)
backend/services/tcp_fingerprint.py
Détection multi-signal de l'OS réel basée sur la stack TCP :
from backend.services.tcp_fingerprint import fingerprint_os, detect_spoof
result = fingerprint_os(ttl=52, win=5808, scale=4, mss=1452)
# → OSFingerprint(os_family="Masscan/Scanner", confidence=0.97, is_bot_tool=True)
spoof = detect_spoof(declared_ua="Chrome/Windows", fingerprint=result)
# → SpoofResult(is_spoof=True, reason="UA Windows mais stack Masscan", risk_score=30)
Poids du scoring : TTL initial 40% + MSS 30% + fenêtre 20% + scale 10%
Estimation hop-count :
- TTL observé 52 → TTL initial arrondi = 64 → hops = 64 − 52 = 12
- TTL observé 119 → TTL initial = 128 → hops = 9
MSS → chemin réseau :
| MSS | Réseau détecté |
|---|---|
| 1460 | Ethernet standard |
| 1452 | PPPoE / DSL |
| 1420–1452 | VPN probable |
| < 1420 | Tunnel / double-encap |
backend/services/clustering_engine.py
K-means++ + PCA-2D embarqués en pur Python (sans numpy/sklearn) :
K-means++ init : O(k·n) distances, n_init=3 runs → meilleure inertie
Power iteration : X^T(Xv) trick → O(n·d) par itération, pas de matrice n×n
Déflation Hotelling : retire PC1 de X avant de calculer PC2
21 features normalisées [0,1] — voir FEATURES dans le fichier.
Nommage automatique par priorité décroissante :
- Pattern Masscan (mss 1440–1460, scale 3–5, TTL<60)
- Fuzzing agressif (fuzzing_index normalisé > 0.35 ≈ valeur brute > 100)
- UA rotatif + UA-CH mismatch simultanés
- UA-CH mismatch seul > 80%
- Score anomalie ML > 20% + signal comportemental
- Classification réseau / OS par TTL/MSS
🗄️ Tables ClickHouse utilisées
| Table / Vue | Routes |
|---|---|
mabase_prod.ml_detected_anomalies |
metrics, detections, variability, analysis, clustering |
mabase_prod.agg_host_ip_ja4_1h |
tcp_spoofing, clustering, investigation_summary |
mabase_prod.view_dashboard_entities |
entities (UA, JA4, paths, query params) |
mabase_prod.classifications |
analysis (classifications SOC manuelles) |
mabase_prod.audit_logs |
audit (optionnel — silencieux si absent) |
Conventions SQL :
- IPs stockées en IPv6-mappé :
replaceRegexpAll(toString(src_ip), '^::ffff:', '') anomaly_scorepeut être négatif : toujours utiliserabs()fuzzing_indexpeut dépasser 200 : normaliser aveclog1pmultiplexing_efficiencypeut dépasser 1 : normaliser aveclog1p- Paramètres SQL : syntaxe
%(name)s(dict ClickHouse) - SPA catch-all DOIT être le dernier router dans
main.py
🎨 Thème
Le dashboard utilise un thème sombre optimisé SOC (dark par défaut, clair et auto disponibles) :
- Tokens CSS sémantiques :
bg-background,bg-background-card,text-text-primary,text-text-secondary… - Taxonomie menaces : rouge CRITICAL / orange HIGH / jaune MEDIUM / vert LOW
- Persistance :
localStorageclésoc_theme - Ne jamais utiliser de classes Tailwind brutes (
slate-800) — toujours les tokens sémantiques
📝 Logs
Les logs du dashboard sont accessibles via Docker :
# Logs du container
docker logs dashboard_web
# Logs en temps réel
docker logs -f dashboard_web
🧪 Tests et Validation
Script de test rapide
Créez un fichier test_dashboard.sh :
#!/bin/bash
echo "=== Test Dashboard Bot Detector ==="
# 1. Health check
echo -n "1. Health check... "
curl -s http://localhost:3000/health > /dev/null && echo "✅ OK" || echo "❌ ÉCHOUÉ"
# 2. API Metrics
echo -n "2. API Metrics... "
curl -s http://localhost:3000/api/metrics | jq -e '.summary' > /dev/null && echo "✅ OK" || echo "❌ ÉCHOUÉ"
# 3. API Detections
echo -n "3. API Detections... "
curl -s http://localhost:3000/api/detections | jq -e '.items' > /dev/null && echo "✅ OK" || echo "❌ ÉCHOUÉ"
# 4. Frontend
echo -n "4. Frontend HTML... "
curl -s http://localhost:3000 | grep -q "Bot Detector" && echo "✅ OK" || echo "❌ ÉCHOUÉ"
echo "=== Tests terminés ==="
Rendez-le exécutable et lancez-le :
chmod +x test_dashboard.sh
./test_dashboard.sh
Tests manuels de l'API
# 1. Health check
curl http://localhost:3000/health
# 2. Métriques globales
curl http://localhost:3000/api/metrics | jq
# 3. Liste des détections (page 1, 25 items)
curl "http://localhost:3000/api/detections?page=1&page_size=25" | jq
# 4. Filtrer par menace CRITICAL
curl "http://localhost:3000/api/detections?threat_level=CRITICAL" | jq '.items[].src_ip'
# 5. Distribution par menace
curl http://localhost:3000/api/metrics/threats | jq
# 6. Liste des IPs uniques (top 10)
curl "http://localhost:3000/api/attributes/ip?limit=10" | jq
# 7. Variabilité d'une IP (remplacer par une IP réelle)
curl http://localhost:3000/api/variability/ip/192.168.1.100 | jq
# 8. Variabilité d'un pays
curl http://localhost:3000/api/variability/country/FR | jq
# 9. Variabilité d'un ASN
curl http://localhost:3000/api/variability/asn/16276 | jq
Test du Frontend
# Vérifier que le HTML est servi
curl -s http://localhost:3000 | head -20
# Ou ouvrir dans le navigateur
# http://localhost:3000
Scénarios de test utilisateur
-
Navigation de base
- Ouvrir http://localhost:3000
- Vérifier que les métriques s'affichent
- Cliquer sur "📋 Détections"
-
Recherche et filtres
- Rechercher une IP :
192.168 - Filtrer par menace : CRITICAL
- Changer de page
- Rechercher une IP :
-
Investigation (variabilité)
- Cliquer sur une IP dans le tableau
- Vérifier la section "User-Agents" (plusieurs valeurs ?)
- Cliquer sur un User-Agent pour investiguer
- Utiliser le breadcrumb pour revenir en arrière
-
Insights
- Trouver une IP avec plusieurs User-Agents
- Vérifier que l'insight "Possible rotation/obfuscation" s'affiche
Vérifier les données ClickHouse
# Compter les détections (24h)
docker compose exec clickhouse clickhouse-client -d mabase_prod -q \
"SELECT count() FROM ml_detected_anomalies WHERE detected_at >= now() - INTERVAL 24 HOUR"
# Voir un échantillon
docker compose exec clickhouse clickhouse-client -d mabase_prod -q \
"SELECT src_ip, threat_level, model_name, detected_at FROM ml_detected_anomalies ORDER BY detected_at DESC LIMIT 5"
# Vérifier les vues du dashboard
docker compose exec clickhouse clickhouse-client -d mabase_prod -q \
"SELECT * FROM view_dashboard_summary"
🐛 Dépannage
Diagnostic rapide
# 1. Vérifier que les services tournent
docker compose ps
# 2. Vérifier les logs du dashboard
docker compose logs dashboard_web | tail -50
# 3. Tester la connexion ClickHouse depuis le dashboard
docker compose exec dashboard_web curl -v http://clickhouse:8123/ping
Le dashboard ne démarre pas
# Vérifier les logs
docker compose logs dashboard_web
# Erreur courante: Port déjà utilisé
# Solution: Changer le port dans docker-compose.yml
# Erreur courante: Image non construite
docker compose build dashboard_web
docker compose up -d dashboard_web
Aucune donnée affichée (dashboard vide)
# 1. Vérifier qu'il y a des données dans ClickHouse
docker compose exec clickhouse clickhouse-client -d mabase_prod -q \
"SELECT count() FROM ml_detected_anomalies WHERE detected_at >= now() - INTERVAL 24 HOUR"
# Si le résultat est 0:
# - Lancer bot_detector_ai pour générer des données
docker compose up -d bot_detector_ai
docker compose logs -f bot_detector_ai
# - Ou importer des données manuellement
Erreur "Connexion ClickHouse échoué"
# 1. Vérifier que ClickHouse est démarré
docker compose ps clickhouse
# 2. Tester la connexion
docker compose exec clickhouse clickhouse-client -q "SELECT 1"
# 3. Vérifier les credentials dans .env
cat .env | grep CLICKHOUSE
# 4. Redémarrer le dashboard
docker compose restart dashboard_web
# 5. Vérifier les logs d'erreur
docker compose logs dashboard_web | grep -i error
Erreur 404 sur les routes API
# Vérifier que l'API répond
curl http://localhost:3000/health
curl http://localhost:3000/api/metrics
# Si 404, redémarrer le dashboard
docker compose restart dashboard_web
Port 3000 déjà utilisé
# Option 1: Changer le port dans docker-compose.yml
# Remplacer: - "3000:8000"
# Par: - "8080:8000"
# Option 2: Trouver et tuer le processus
lsof -i :3000
kill <PID>
# Puis redémarrer
docker compose up -d dashboard_web
Frontend ne se charge pas (page blanche)
# 1. Vérifier la console du navigateur (F12)
# 2. Vérifier que le build frontend existe
docker compose exec dashboard_web ls -la /app/frontend/dist
# 3. Si vide, reconstruire l'image
docker compose build --no-cache dashboard_web
docker compose up -d dashboard_web
Logs d'erreur courants
| Erreur | Cause | Solution |
|---|---|---|
Connection refused |
ClickHouse pas démarré | docker compose up -d clickhouse |
Authentication failed |
Mauvais credentials | Vérifier .env |
Table doesn't exist |
Vues non créées | Lancer deploy_views.sql |
No data available |
Pas de données | Lancer bot_detector_ai |
🔒 Sécurité
- Pas d'authentification : Dashboard conçu pour un usage local
- CORS restreint : Seulement localhost:3000
- Rate limiting : 100 requêtes/minute
- Credentials : Via variables d'environnement (jamais en dur)
📊 Performances
- Temps de chargement : < 2s (avec données)
- Requêtes ClickHouse : Optimisées avec agrégations
- Rafraîchissement auto : 30 secondes (métriques)
🧪 Développement
Build local (sans Docker)
# Backend
cd dashboard
pip install -r requirements.txt
python -m uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
# Frontend (dans un autre terminal)
cd dashboard/frontend
npm install
npm run dev # http://localhost:5173
Documentation API interactive
L'API inclut une documentation Swagger interactive :
# Ouvrir dans le navigateur
http://localhost:3000/docs
# Ou directement sur le port API
http://localhost:8000/docs
Tests unitaires (à venir)
# Backend (pytest)
cd dashboard
pytest backend/tests/
# Frontend (jest)
cd dashboard/frontend
npm test
📄 License
Même license que le projet principal Bot Detector.
📞 Support
Pour toute question ou problème :
- Vérifier la section 🐛 Dépannage ci-dessus
- Consulter les logs :
docker compose logs dashboard_web - Vérifier que ClickHouse contient des données
- Ouvrir une issue sur le dépôt