File size: 38,633 Bytes
687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 |
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) |