File size: 35,286 Bytes
687de48 b237e11 687de48 b237e11 687de48 b237e11 4a102e1 b237e11 4a102e1 b237e11 687de48 4a102e1 b237e11 687de48 4a102e1 b237e11 4a102e1 687de48 b237e11 687de48 b237e11 4a102e1 b237e11 4a102e1 b237e11 687de48 b237e11 4a102e1 b237e11 687de48 b237e11 4a102e1 b237e11 4a102e1 b237e11 687de48 b237e11 687de48 b237e11 4a102e1 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 4a102e1 b237e11 687de48 b237e11 687de48 b237e11 4a102e1 b237e11 687de48 b237e11 687de48 b237e11 687de48 b237e11 4a102e1 b237e11 687de48 4a102e1 |
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 |
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)
|