|
from flask import Flask, render_template, request, jsonify, redirect, url_for |
|
import asyncio |
|
import aiohttp |
|
import os |
|
import json |
|
import time |
|
import random |
|
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 |
|
|
|
|
|
def default_json(o): |
|
if isinstance(o, Undefined): |
|
return '' |
|
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") |
|
|
|
|
|
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__) |
|
|
|
|
|
from flask.json.provider import DefaultJSONProvider |
|
|
|
class CustomJSONProvider(DefaultJSONProvider): |
|
def default(self, o): |
|
if isinstance(o, Undefined): |
|
return "" |
|
return super().default(o) |
|
|
|
|
|
app.json = CustomJSONProvider(app) |
|
|
|
|
|
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() |
|
process_pool = None |
|
background_tasks = [] |
|
|
|
|
|
MAX_CONNECTIONS = 1000 |
|
SAVE_FREQUENCY = 100 |
|
CHUNK_SIZE = 5000 |
|
MAX_RETRIES = 5 |
|
RETRY_DELAY = 0.5 |
|
REQUESTS_FILE = "requetes_gabaohub.json" |
|
IP_ROTATION_COUNT = 50 |
|
REQUEST_TIMEOUT = 15 |
|
|
|
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/", |
|
"" |
|
] |
|
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) |
|
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" |
|
} |
|
) |
|
|
|
|
|
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 |
|
}) |
|
|
|
|
|
@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) |
|
|