Files
ja4-platform/services/dashboard
toto ecceb04174 perf(clickhouse): P3 — view_ip_recurrence avec filtre TTL + supprimer FINAL
view_ip_recurrence :
  Ajout de WHERE detected_at >= now() - INTERVAL 30 DAY
  → Avec PARTITION BY (P1), ClickHouse élagage les partitions hors de cette
    plage avant même de lire les données. La vue ne scanne que les partitions
    actives (au lieu des 30 partitions journalières complètes).
  → ORDER BY (src_ip) garantit que le GROUP BY src_ip lit des données
    contiguës (aucune réorganisation mémoire).

rotation.py — supprimer FINAL sur ml_detected_anomalies :
  FINAL force une déduplication complète du ReplacingMergeTree en mémoire
  (équivalent à un DISTINCT sur toute la table) — une des opérations les plus
  coûteuses dans ClickHouse.
  Fix : remplacer le sous-SELECT FINAL par view_ip_recurrence (déjà aggrégée
  par src_ip, retourne recurrence directement sans FINAL).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-07 22:33:29 +02:00
..

🛡️ 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 clickhouse dé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 0100% : 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 0100, 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 ja4_processing 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 .env contient les credentials réels — ne jamais le committer.

🔍 Workflows d'Investigation

Exemple 1 : Identifier un bot Masscan

  1. 🔬 Clustering IPs → Cluster "🤖 Masscan / Scanner IP" visible en rouge
  2. Clic sur la carte → Sidebar : TTL=52, MSS=1452, Scale=4 — pattern Masscan
  3. Copier les IPs → Liste prête pour le blocage
  4. Export CSV → Import dans le SIEM ou firewall

Exemple 2 : Analyser des bots UA-rotatifs (cloud)

  1. Clustering → Cluster "🤖 Bot UA Rotatif + CH Mismatch" (risque 50%)
  2. RadarChart → UA-CH=100%, UA rotatif=100%, anomalie=59%
  3. Top ASN → Microsoft, Google, Akamai — cloud providers
  4. 🧬 TCP Spoofing → Confirmer : ces IPs déclarent Windows UA mais ont TTL Linux
  5. Investigation IP → Détail complet avec timeline 24h

Exemple 3 : Détecter le spoofing d'OS

  1. 🧬 TCP Spoofing → Liste des IPs avec mismatch OS
  2. Matrice UA×OS → User-Agent Android mais stack TCP Windows = spoof
  3. Confiance 85% → MSS=1460 (Ethernet), scale=7, TTL≈64 → Linux réel
  4. Action → Classer comme bot avec IP proxy

Exemple 4 : Investiguer une IP suspecte

  1. 🎯 Détections → IP classifiée 🔴 CRITICAL
  2. Clic sur l'IP → Synthèse : ML + TCP + JA4 + bruteforce + timeline
  3. Score de risque : 85/100
  4. User-Agents → 3 UA différents en 24h (rotation)
  5. TCP → TTL initial 128 (Windows) mais UA Linux → spoof
  6. 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
14201452 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 :

  1. Pattern Masscan (mss 14401460, scale 35, TTL<60)
  2. Fuzzing agressif (fuzzing_index normalisé > 0.35 ≈ valeur brute > 100)
  3. UA rotatif + UA-CH mismatch simultanés
  4. UA-CH mismatch seul > 80%
  5. Score anomalie ML > 20% + signal comportemental
  6. Classification réseau / OS par TTL/MSS

🗄️ Tables ClickHouse utilisées

Table / Vue Routes
ja4_processing.ml_detected_anomalies metrics, detections, variability, analysis, clustering
ja4_processing.agg_host_ip_ja4_1h tcp_spoofing, clustering, investigation_summary
ja4_processing.view_dashboard_entities entities (UA, JA4, paths, query params)
ja4_processing.classifications analysis (classifications SOC manuelles)
ja4_processing.audit_logs audit (optionnel — silencieux si absent)

Conventions SQL :

  • IPs stockées en IPv6-mappé : replaceRegexpAll(toString(src_ip), '^::ffff:', '')
  • anomaly_score peut être négatif : toujours utiliser abs()
  • fuzzing_index peut dépasser 200 : normaliser avec log1p
  • multiplexing_efficiency peut dépasser 1 : normaliser avec log1p
  • 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 : localStorage clé 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

  1. Navigation de base

  2. Recherche et filtres

    • Rechercher une IP : 192.168
    • Filtrer par menace : CRITICAL
    • Changer de page
  3. 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
  4. 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 ja4_processing -q \
  "SELECT count() FROM ml_detected_anomalies WHERE detected_at >= now() - INTERVAL 24 HOUR"

# Voir un échantillon
docker compose exec clickhouse clickhouse-client -d ja4_processing -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 ja4_processing -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 ja4_processing -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 :

  1. Vérifier la section 🐛 Dépannage ci-dessus
  2. Consulter les logs : docker compose logs dashboard_web
  3. Vérifier que ClickHouse contient des données
  4. Ouvrir une issue sur le dépôt