Docfile commited on
Commit
1f8b86f
·
verified ·
1 Parent(s): 4854cab

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +277 -544
templates/index.html CHANGED
@@ -1,572 +1,305 @@
1
- <!DOCTYPE html>
2
- <html lang="fr">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Générateur de Comptes GabaoHub</title>
7
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
8
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
9
- <style>
10
- body {
11
- padding-top: 2rem;
12
- background-color: #f8f9fa;
13
- }
14
- .card {
15
- margin-bottom: 20px;
16
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
17
- border-radius: 10px;
18
- overflow: hidden;
19
- }
20
- .card-header {
21
- background-color: #4c5daf;
22
- color: white;
23
- padding: 1rem;
24
- }
25
- .success {
26
- background-color: #d4edda;
27
- }
28
- .failed {
29
- background-color: #f8d7da;
30
- }
31
- .pending {
32
- background-color: #fff3cd;
33
- }
34
- .account-details {
35
- font-family: monospace;
36
- font-size: 0.9rem;
37
- }
38
- .progress {
39
- height: 25px;
40
- border-radius: 15px;
41
- }
42
- #status-badge {
43
- font-size: 1rem;
44
- }
45
- .settings-panel {
46
- background-color: #f1f3f9;
47
- border-radius: 8px;
48
- padding: 15px;
49
- margin-bottom: 20px;
50
- }
51
- .copy-btn {
52
- cursor: pointer;
53
- }
54
- .export-options {
55
- margin-bottom: 15px;
56
- }
57
- .stats-card {
58
- background-color: #e9ecef;
59
- border-radius: 8px;
60
- padding: 15px;
61
- margin-bottom: 15px;
62
- transition: all 0.3s ease;
63
- }
64
- .stats-card:hover {
65
- transform: translateY(-3px);
66
- box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
67
- }
68
- .stats-value {
69
- font-size: 1.5rem;
70
- font-weight: bold;
71
- }
72
- .table-responsive {
73
- border-radius: 8px;
74
- overflow: hidden;
75
- }
76
- /* Animation pour les nouveaux comptes créés */
77
- @keyframes highlight {
78
- 0% { background-color: #fff9c4; }
79
- 100% { background-color: transparent; }
80
- }
81
- .highlight-new {
82
- animation: highlight 2s ease-in-out;
83
- }
84
- </style>
85
- </head>
86
- <body>
87
- <div class="container">
88
- <div class="row justify-content-center">
89
- <div class="col-md-10">
90
- <div class="card">
91
- <div class="card-header d-flex justify-content-between align-items-center">
92
- <h2><i class="bi bi-person-plus-fill"></i> Générateur de Comptes GabaoHub</h2>
93
- <span id="status-badge" class="badge {% if creation_in_progress %}bg-warning{% else %}bg-success{% endif %}">
94
- {% if creation_in_progress %}
95
- <i class="bi bi-hourglass-split"></i> En cours
96
- {% else %}
97
- <i class="bi bi-check-circle"></i> Prêt
98
- {% endif %}
99
- </span>
100
- </div>
101
- <div class="card-body">
102
- <div class="settings-panel mb-4">
103
- <h5><i class="bi bi-gear-fill"></i> Paramètres de création</h5>
104
- <form action="/start" method="post" class="mb-3" id="creation-form">
105
- <div class="row g-3">
106
- <div class="col-md-6">
107
- <label for="num_accounts" class="form-label">Nombre de comptes</label>
108
- <div class="input-group">
109
- <span class="input-group-text"><i class="bi bi-people-fill"></i></span>
110
- <input type="number" class="form-control" id="num_accounts" name="num_accounts" value="100" min="1" max="100000" {% if creation_in_progress %}disabled{% endif %}>
111
- </div>
112
- </div>
113
- <div class="col-md-6">
114
- <label for="concurrency" class="form-label">Concurrence</label>
115
- <div class="input-group">
116
- <span class="input-group-text"><i class="bi bi-speedometer"></i></span>
117
- <input type="number" class="form-control" id="concurrency" name="concurrency" value="5" min="1" max="10" {% if creation_in_progress %}disabled{% endif %}>
118
- <button type="submit" class="btn btn-primary" {% if creation_in_progress %}disabled{% endif %}>
119
- <i class="bi bi-play-fill"></i> Démarrer
120
- </button>
121
- </div>
122
- <small class="text-muted">Nombre de tâches simultanées (1-10)</small>
123
- </div>
124
- </div>
125
- </form>
126
- </div>
127
-
128
- {% if creation_in_progress or progress > 0 %}
129
- <div class="mb-4">
130
- <h5><i class="bi bi-graph-up"></i> Progression</h5>
131
- <div class="progress mb-2">
132
- <div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated bg-primary"
133
- role="progressbar" style="width: {{ (progress / total * 100) if total > 0 else 0 }}%;"
134
- aria-valuenow="{{ progress }}" aria-valuemin="0" aria-valuemax="{{ total }}">
135
- {{ progress }}/{{ total }}
136
- </div>
137
  </div>
138
- <p id="progress-text" class="text-muted">
139
- <i class="bi bi-info-circle"></i> {{ progress }} compte(s) sur {{ total }} créé(s)
140
- {% if creation_in_progress %}
141
- <span class="spinner-border spinner-border-sm text-primary ms-2" role="status"></span>
142
- {% endif %}
143
- </p>
144
- </div>
145
-
146
- <div class="row mb-4">
147
- <div class="col-md-4">
148
- <div class="stats-card text-center">
149
- <i class="bi bi-check-circle-fill text-success mb-2" style="font-size: 1.5rem;"></i>
150
- <h6>Réussis</h6>
151
- <div id="success-count" class="stats-value text-success">{{ accounts|selectattr('status', 'equalto', 'success')|list|length }}</div>
152
- </div>
153
  </div>
154
- <div class="col-md-4">
155
- <div class="stats-card text-center">
156
- <i class="bi bi-x-circle-fill text-danger mb-2" style="font-size: 1.5rem;"></i>
157
- <h6>Échoués</h6>
158
- <div id="failed-count" class="stats-value text-danger">{{ accounts|selectattr('status', 'equalto', 'failed')|list|length }}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  </div>
160
  </div>
161
- <div class="col-md-4">
162
- <div class="stats-card text-center">
163
- <i class="bi bi-hourglass-split text-warning mb-2" style="font-size: 1.5rem;"></i>
164
- <h6>En attente</h6>
165
- <div id="pending-count" class="stats-value text-warning">{{ accounts|selectattr('status', 'equalto', 'pending')|list|length }}</div>
 
166
  </div>
167
  </div>
168
  </div>
169
- {% endif %}
170
-
171
- {% if accounts and accounts|length > 0 %}
172
- <div>
173
- <div class="d-flex justify-content-between align-items-center mb-3">
174
- <h5><i class="bi bi-person-lines-fill"></i> Comptes créés</h5>
175
- <div class="d-flex">
176
- <div class="export-options me-2">
177
- <div class="dropdown">
178
- <button class="btn btn-outline-secondary dropdown-toggle" type="button" id="exportDropdown" data-bs-toggle="dropdown" aria-expanded="false">
179
- <i class="bi bi-download"></i> Exporter
180
- </button>
181
- <ul class="dropdown-menu" aria-labelledby="exportDropdown">
182
- <li><a class="dropdown-item" href="#" id="export-all"><i class="bi bi-file-earmark-text"></i> Tous les comptes</a></li>
183
- <li><a class="dropdown-item" href="#" id="export-success"><i class="bi bi-file-earmark-check"></i> Comptes réussis</a></li>
184
- <li><hr class="dropdown-divider"></li>
185
- <li><a class="dropdown-item" href="#" id="copy-table"><i class="bi bi-clipboard"></i> Copier le tableau</a></li>
186
- </ul>
187
- </div>
188
- </div>
189
- <form action="/reset" method="post" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer toutes les données?');">
190
- <button type="submit" class="btn btn-danger" {% if creation_in_progress %}disabled{% endif %}>
191
- <i class="bi bi-trash"></i> Réinitialiser
192
- </button>
193
- </form>
194
  </div>
195
  </div>
196
-
197
- <div class="table-responsive">
198
- <table class="table table-striped table-hover" id="accounts-table">
199
- <thead class="table-dark">
200
- <tr>
201
- <th>#</th>
202
- <th>Nom d'utilisateur</th>
203
- <th>Mot de passe</th>
204
- <th>Province</th>
205
- <th>Ville</th>
206
- <th>Statut</th>
207
- <th>Horodatage</th>
208
- <th>Actions</th>
209
- </tr>
210
- </thead>
211
- <tbody id="accounts-table-body">
212
- {% for account in accounts %}
213
- <tr class="{{ account.status }}">
214
- <td>{{ loop.index }}</td>
215
- <td>{{ account.username }}</td>
216
- <td>
217
- <span class="password-text">{{ account.password }}</span>
218
- <i class="bi bi-clipboard copy-btn ms-2" data-clipboard-text="{{ account.password }}" title="Copier le mot de passe"></i>
219
- </td>
220
- <td>{{ account.province }}</td>
221
- <td>{{ account.ville }}</td>
222
- <td>
223
- <span class="badge {% if account.status == 'success' %}bg-success{% elif account.status == 'failed' %}bg-danger{% else %}bg-warning{% endif %}">
224
- {% if account.status == 'success' %}
225
- <i class="bi bi-check-circle-fill"></i>
226
- {% elif account.status == 'failed' %}
227
- <i class="bi bi-x-circle-fill"></i>
228
- {% else %}
229
- <i class="bi bi-hourglass-split"></i>
230
- {% endif %}
231
- {{ account.status }}
232
- </span>
233
- </td>
234
- <td>{{ account.timestamp if account.timestamp else 'N/A' }}</td>
235
- <td>
236
- <button class="btn btn-sm btn-outline-primary copy-account" data-username="{{ account.username }}" data-password="{{ account.password }}">
237
- <i class="bi bi-clipboard-check"></i>
238
- </button>
239
- </td>
240
- </tr>
241
- {% endfor %}
242
- </tbody>
243
- </table>
244
- </div>
245
- </div>
246
- {% else %}
247
- <div class="alert alert-info">
248
- <i class="bi bi-info-circle-fill"></i> Aucun compte n'a encore été créé. Cliquez sur "Démarrer" pour commencer.
249
  </div>
250
- {% endif %}
251
  </div>
252
  </div>
253
  </div>
254
- </div>
255
- </div>
256
-
257
- <!-- Toast pour les notifications -->
258
- <div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
259
- <div id="copyToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
260
- <div class="toast-header bg-success text-white">
261
- <i class="bi bi-check-circle-fill me-2"></i>
262
- <strong class="me-auto">Notification</strong>
263
- <button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
264
  </div>
265
- <div class="toast-body" id="toast-message">
266
- Copié avec succès!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  </div>
268
  </div>
269
- </div>
270
-
271
- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
272
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
273
- <script>
274
- // Toast pour afficher les notifications
275
- function showToast(message, type = 'success') {
276
- const toast = $('#copyToast');
277
- const toastHeader = toast.find('.toast-header');
278
-
279
- // Définir le type de toast
280
- if (type === 'success') {
281
- toastHeader.removeClass('bg-danger').addClass('bg-success');
282
- toastHeader.find('i').removeClass('bi-x-circle-fill').addClass('bi-check-circle-fill');
283
- } else {
284
- toastHeader.removeClass('bg-success').addClass('bg-danger');
285
- toastHeader.find('i').removeClass('bi-check-circle-fill').addClass('bi-x-circle-fill');
286
- }
287
 
288
- // Mettre à jour le message
289
- $('#toast-message').text(message);
290
-
291
- // Afficher le toast
292
- const bsToast = new bootstrap.Toast(toast);
293
- bsToast.show();
294
- }
295
-
296
- // Fonction pour copier du texte dans le presse-papiers
297
- function copyToClipboard(text) {
298
- navigator.clipboard.writeText(text).then(function() {
299
- showToast('Copié avec succès!');
300
- }, function() {
301
- showToast('Échec de la copie', 'error');
302
- });
303
- }
304
-
305
- // Fonction pour exporter les données en CSV
306
- function exportToCSV(accounts, onlySuccess = false) {
307
- if (!accounts || accounts.length === 0) return;
308
-
309
- // Filtrer les comptes si nécessaire
310
- const dataToExport = onlySuccess ? accounts.filter(a => a.status === 'success') : accounts;
311
-
312
- // Créer les en-têtes CSV
313
- let csv = 'Username,Password,Province,Ville,Status,Timestamp\n';
314
-
315
- // Ajouter les données
316
- dataToExport.forEach(account => {
317
- const username = account.username || '';
318
- const password = account.password || '';
319
- const province = account.province || '';
320
- const ville = account.ville || '';
321
- const status = account.status || '';
322
- const timestamp = account.timestamp || '';
323
 
324
- csv += `"${username}","${password}","${province}","${ville}","${status}","${timestamp}"\n`;
325
- });
 
 
 
 
 
326
 
327
- // Créer un blob et télécharger
328
- const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
329
- const url = URL.createObjectURL(blob);
330
- const link = document.createElement('a');
331
- link.setAttribute('href', url);
332
- link.setAttribute('download', onlySuccess ? 'gabao_hub_success_accounts.csv' : 'gabao_hub_all_accounts.csv');
333
- document.body.appendChild(link);
334
- link.click();
335
- document.body.removeChild(link);
336
- }
337
-
338
- // Fonction pour mettre à jour les informations sur la page
339
- function updateProgress() {
340
- if ({{ creation_in_progress|tojson }}) {
341
- $.ajax({
342
- url: '/progress',
343
- type: 'GET',
344
- dataType: 'json',
345
- success: function(data) {
346
- // Mettre à jour l'état
347
- if (data.creation_in_progress) {
348
- $('#status-badge').html('<i class="bi bi-hourglass-split"></i> En cours').removeClass('bg-success').addClass('bg-warning');
349
- $('#creation-form button').prop('disabled', true);
350
- $('#num_accounts').prop('disabled', true);
351
- $('#concurrency').prop('disabled', true);
352
- $('form[action="/reset"] button').prop('disabled', true);
353
- } else {
354
- $('#status-badge').html('<i class="bi bi-check-circle"></i> Prêt').removeClass('bg-warning').addClass('bg-success');
355
- $('#creation-form button').prop('disabled', false);
356
- $('#num_accounts').prop('disabled', false);
357
- $('#concurrency').prop('disabled', false);
358
- $('form[action="/reset"] button').prop('disabled', false);
359
- }
360
 
361
- // Mettre à jour la barre de progression
362
- let percentage = data.total > 0 ? (data.progress / data.total * 100) : 0;
363
- $('#progress-bar').css('width', percentage + '%').attr('aria-valuenow', data.progress);
364
- $('#progress-bar').text(data.progress + '/' + data.total);
365
 
366
- let progressText = data.progress + ' compte(s) sur ' + data.total + ' créé(s)';
367
- if (data.creation_in_progress) {
368
- progressText += ' <span class="spinner-border spinner-border-sm text-primary ms-2" role="status"></span>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  }
370
- $('#progress-text').html('<i class="bi bi-info-circle"></i> ' + progressText);
371
 
372
- // Mettre à jour les compteurs statistiques
373
- updateStatCounters(data.accounts);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
- // Mettre à jour le tableau des comptes
376
- updateAccountsTable(data.accounts);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
- // Continuer la mise à jour uniquement si la création est en cours
379
- if (data.creation_in_progress) {
380
- setTimeout(updateProgress, 2000);
 
 
 
 
 
 
 
 
 
381
  } else {
382
- // Rechargement de la page pour afficher les résultats finaux
383
- location.reload();
384
  }
385
- }
386
- });
387
- }
388
- }
389
-
390
- // Fonction pour mettre à jour les compteurs statistiques
391
- function updateStatCounters(accounts) {
392
- if (!accounts || accounts.length === 0) return;
393
-
394
- const successCount = accounts.filter(a => a.status === 'success').length;
395
- const failedCount = accounts.filter(a => a.status === 'failed').length;
396
- const pendingCount = accounts.filter(a => a.status === 'pending').length;
397
-
398
- $('#success-count').text(successCount);
399
- $('#failed-count').text(failedCount);
400
- $('#pending-count').text(pendingCount);
401
- }
402
-
403
- // Fonction pour mettre à jour le tableau des comptes
404
- function updateAccountsTable(accounts) {
405
- if (!accounts || accounts.length === 0) return;
406
-
407
- let tableBody = $('#accounts-table-body');
408
- let existingRows = tableBody.find('tr').length;
409
-
410
- // Parcourir les comptes et mettre à jour le tableau
411
- accounts.forEach(function(account, index) {
412
- let statusClass = '';
413
- let statusIcon = '';
414
- let statusBadge = '';
415
-
416
- if (account.status === 'success') {
417
- statusClass = 'success';
418
- statusIcon = '<i class="bi bi-check-circle-fill"></i>';
419
- statusBadge = '<span class="badge bg-success">' + statusIcon + ' success</span>';
420
- } else if (account.status === 'failed') {
421
- statusClass = 'failed';
422
- statusIcon = '<i class="bi bi-x-circle-fill"></i>';
423
- statusBadge = '<span class="badge bg-danger">' + statusIcon + ' failed</span>';
424
- } else {
425
- statusClass = 'pending';
426
- statusIcon = '<i class="bi bi-hourglass-split"></i>';
427
- statusBadge = '<span class="badge bg-warning">' + statusIcon + ' pending</span>';
428
- }
429
-
430
- const timestamp = account.timestamp ? account.timestamp : 'N/A';
431
- const username = account.username || '';
432
- const password = account.password || '';
433
- const province = account.province || '';
434
- const ville = account.ville || '';
435
-
436
- // Vérifier si cette ligne existe déjà
437
- let existingRow = tableBody.find('tr').eq(index);
438
-
439
- if (existingRow.length) {
440
- // Mettre à jour la ligne existante
441
- existingRow.attr('class', statusClass);
442
- existingRow.find('td').eq(1).text(username);
443
-
444
- let passwordCell = existingRow.find('td').eq(2);
445
- passwordCell.html('<span class="password-text">' + password + '</span><i class="bi bi-clipboard copy-btn ms-2" data-clipboard-text="' + password + '" title="Copier le mot de passe"></i>');
446
-
447
- existingRow.find('td').eq(3).text(province);
448
- existingRow.find('td').eq(4).text(ville);
449
- existingRow.find('td').eq(5).html(statusBadge);
450
- existingRow.find('td').eq(6).text(timestamp);
451
-
452
- // Mettre à jour le bouton de copie
453
- let actionsCell = existingRow.find('td').eq(7);
454
- actionsCell.html('<button class="btn btn-sm btn-outline-primary copy-account" data-username="' + username + '" data-password="' + password + '"><i class="bi bi-clipboard-check"></i></button>');
455
-
456
- // Ajouter animation si le statut a changé de "pending"
457
- if (existingRow.data('status') === 'pending' && account.status !== 'pending') {
458
- existingRow.addClass('highlight-new');
459
- setTimeout(function() {
460
- existingRow.removeClass('highlight-new');
461
- }, 2000);
462
- }
463
-
464
- // Stocker le statut actuel
465
- existingRow.data('status', account.status);
466
-
467
- } else {
468
- // Ajouter une nouvelle ligne
469
- let newRow = $(`
470
- <tr class="${statusClass}" data-status="${account.status}">
471
- <td>${index + 1}</td>
472
- <td>${username}</td>
473
- <td>
474
- <span class="password-text">${password}</span>
475
- <i class="bi bi-clipboard copy-btn ms-2" data-clipboard-text="${password}" title="Copier le mot de passe"></i>
476
- </td>
477
- <td>${province}</td>
478
- <td>${ville}</td>
479
- <td>${statusBadge}</td>
480
- <td>${timestamp}</td>
481
- <td>
482
- <button class="btn btn-sm btn-outline-primary copy-account" data-username="${username}" data-password="${password}">
483
- <i class="bi bi-clipboard-check"></i>
484
- </button>
485
- </td>
486
- </tr>
487
- `);
488
-
489
- tableBody.append(newRow);
490
- newRow.addClass('highlight-new');
491
- setTimeout(function() {
492
- newRow.removeClass('highlight-new');
493
- }, 2000);
494
- }
495
- });
496
- }
497
-
498
- // Lancer la mise à jour de la progression au chargement de la page
499
- $(document).ready(function() {
500
- // Initialiser les gestionnaires d'événements
501
-
502
- // Copier le mot de passe
503
- $(document).on('click', '.copy-btn', function() {
504
- const text = $(this).data('clipboard-text');
505
- copyToClipboard(text);
506
- });
507
-
508
- // Copier les informations complètes du compte
509
- $(document).on('click', '.copy-account', function() {
510
- const username = $(this).data('username');
511
- const password = $(this).data('password');
512
- const accountInfo = `Nom d'utilisateur: ${username}\nMot de passe: ${password}`;
513
- copyToClipboard(accountInfo);
514
- });
515
-
516
- // Exporter tous les comptes
517
- $('#export-all').click(function(e) {
518
- e.preventDefault();
519
- $.ajax({
520
- url: '/progress',
521
- type: 'GET',
522
- dataType: 'json',
523
- success: function(data) {
524
- exportToCSV(data.accounts, false);
525
- }
526
- });
527
- });
528
-
529
- // Exporter uniquement les comptes réussis
530
- $('#export-success').click(function(e) {
531
- e.preventDefault();
532
- $.ajax({
533
- url: '/progress',
534
- type: 'GET',
535
- dataType: 'json',
536
- success: function(data) {
537
- exportToCSV(data.accounts, true);
538
- }
539
- });
540
- });
541
-
542
- // Copier le tableau entier
543
- $('#copy-table').click(function(e) {
544
- e.preventDefault();
545
-
546
- // Construire un tableau formaté pour le presse-papiers
547
- let tableText = "Index\tNom d'utilisateur\tMot de passe\tProvince\tVille\tStatut\tHorodatage\n";
548
-
549
- $('#accounts-table tbody tr').each(function() {
550
- const row = $(this);
551
- const index = row.find('td').eq(0).text();
552
- const username = row.find('td').eq(1).text();
553
- const password = row.find('.password-text').text();
554
- const province = row.find('td').eq(3).text();
555
- const ville = row.find('td').eq(4).text();
556
- const status = row.hasClass('success') ? 'success' : (row.hasClass('failed') ? 'failed' : 'pending');
557
- const timestamp = row.find('td').eq(6).text();
558
-
559
- tableText += `${index}\t${username}\t${password}\t${province}\t${ville}\t${status}\t${timestamp}\n`;
560
- });
561
-
562
- copyToClipboard(tableText);
563
- });
564
-
565
- // Lancer la mise à jour si création en cours
566
- if ({{ creation_in_progress|tojson }}) {
567
- setTimeout(updateProgress, 1000);
568
  }
569
- });
570
- </script>
571
- </body>
572
- </html>
 
1
+
2
+ <!DOCTYPE html>
3
+ <html lang="fr">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Générateur de requêtes pour gabaohub.alwaysdata.net</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <style>
10
+ .status-badge {
11
+ display: inline-block;
12
+ padding: 0.25em 0.6em;
13
+ font-size: 75%;
14
+ font-weight: 700;
15
+ line-height: 1;
16
+ text-align: center;
17
+ white-space: nowrap;
18
+ vertical-align: baseline;
19
+ border-radius: 0.25rem;
20
+ }
21
+ .status-pending { background-color: #6c757d; color: white; }
22
+ .status-in_progress { background-color: #17a2b8; color: white; }
23
+ .status-success { background-color: #28a745; color: white; }
24
+ .status-failed { background-color: #dc3545; color: white; }
25
+ .refresh-icon { cursor: pointer; }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <div class="container mt-4">
30
+ <h1 class="mb-4">Générateur de requêtes pour gabaohub.alwaysdata.net</h1>
31
+
32
+ <div class="card mb-4">
33
+ <div class="card-header">
34
+ Contrôles
35
+ </div>
36
+ <div class="card-body">
37
+ <form action="/start" method="post" class="mb-3">
38
+ <div class="row mb-3">
39
+ <div class="col-md-6">
40
+ <label for="num_requests" class="form-label">Nombre de requêtes à envoyer:</label>
41
+ <input type="number" class="form-control" id="num_requests" name="num_requests" min="1" value="1000">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  </div>
43
+ <div class="col-md-6">
44
+ <label for="concurrency" class="form-label">Niveau de concurrence:</label>
45
+ <input type="number" class="form-control" id="concurrency" name="concurrency" min="1" max="1000" value="100">
 
 
 
 
 
 
 
 
 
 
 
 
46
  </div>
47
+ </div>
48
+ <button type="submit" class="btn btn-primary" id="start-button">Démarrer</button>
49
+ </form>
50
+
51
+ <div class="d-flex">
52
+ <form action="/stop" method="post" class="me-2">
53
+ <button type="submit" class="btn btn-warning" id="stop-button">Arrêter</button>
54
+ </form>
55
+ <form action="/reset" method="post">
56
+ <button type="submit" class="btn btn-danger" id="reset-button">Réinitialiser</button>
57
+ </form>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="card mb-4">
63
+ <div class="card-header d-flex justify-content-between align-items-center">
64
+ <span>Progression</span>
65
+ <span class="refresh-icon" onclick="updateProgress()">🔄</span>
66
+ </div>
67
+ <div class="card-body">
68
+ <div class="progress mb-3">
69
+ <div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
70
+ </div>
71
+ <p id="progress-text">0 / 0 requêtes traitées (0%)</p>
72
+ <p id="status-text">État: Inactif</p>
73
+
74
+ <div class="row mt-4">
75
+ <div class="col-md-4">
76
+ <div class="card bg-success text-white">
77
+ <div class="card-body">
78
+ <h5 class="card-title">Réussies</h5>
79
+ <h2 id="success-count">0</h2>
80
  </div>
81
  </div>
82
+ </div>
83
+ <div class="col-md-4">
84
+ <div class="card bg-danger text-white">
85
+ <div class="card-body">
86
+ <h5 class="card-title">Échouées</h5>
87
+ <h2 id="failed-count">0</h2>
88
  </div>
89
  </div>
90
  </div>
91
+ <div class="col-md-4">
92
+ <div class="card bg-secondary text-white">
93
+ <div class="card-body">
94
+ <h5 class="card-title">En attente</h5>
95
+ <h2 id="pending-count">0</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  </div>
97
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  </div>
 
99
  </div>
100
  </div>
101
  </div>
102
+
103
+ <div class="card mb-4">
104
+ <div class="card-header d-flex justify-content-between align-items-center">
105
+ <span>Statistiques</span>
106
+ <span class="refresh-icon" onclick="updateStats()">🔄</span>
107
+ </div>
108
+ <div class="card-body" id="stats-container">
109
+ <p>Aucune statistique disponible.</p>
110
+ </div>
 
111
  </div>
112
+
113
+ <div class="card">
114
+ <div class="card-header d-flex justify-content-between align-items-center">
115
+ <span>Dernières requêtes</span>
116
+ <span class="refresh-icon" onclick="updateRequests()">🔄</span>
117
+ </div>
118
+ <div class="card-body">
119
+ <div class="table-responsive">
120
+ <table class="table table-striped">
121
+ <thead>
122
+ <tr>
123
+ <th>#</th>
124
+ <th>URL</th>
125
+ <th>Statut</th>
126
+ <th>Code</th>
127
+ <th>Temps (s)</th>
128
+ <th>Heure</th>
129
+ </tr>
130
+ </thead>
131
+ <tbody id="requests-table">
132
+ <tr>
133
+ <td colspan="6" class="text-center">Chargement des données...</td>
134
+ </tr>
135
+ </tbody>
136
+ </table>
137
+ </div>
138
+ </div>
139
  </div>
140
  </div>
141
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
142
+ <script>
143
+ let isPolling = false;
144
+ let pollingInterval;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
+ window.onload = function() {
147
+ updateProgress();
148
+ updateRequests();
149
+ updateStats();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ pollingInterval = setInterval(() => {
152
+ if (isPolling) {
153
+ updateProgress();
154
+ updateStats();
155
+ }
156
+ }, 5000);
157
+ };
158
 
159
+ function updateProgress() {
160
+ fetch('/status')
161
+ .then(response => response.json())
162
+ .then(data => {
163
+ const percentage = data.total > 0 ? (data.progress / data.total * 100) : 0;
164
+ document.getElementById('progress-bar').style.width = percentage + '%';
165
+ document.getElementById('progress-bar').setAttribute('aria-valuenow', percentage);
166
+
167
+ document.getElementById('progress-text').textContent =
168
+ `${data.progress} / ${data.total} requêtes traitées (${percentage.toFixed(1)}%)`;
169
+ document.getElementById('status-text').textContent =
170
+ `État: ${data.requests_in_progress ? 'En cours' : 'Inactif'}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ document.getElementById('success-count').textContent = data.success_count;
173
+ document.getElementById('failed-count').textContent = data.failed_count;
174
+ document.getElementById('pending-count').textContent = data.pending_count;
 
175
 
176
+ isPolling = data.requests_in_progress;
177
+ document.getElementById('start-button').disabled = data.requests_in_progress;
178
+ document.getElementById('stop-button').disabled = !data.requests_in_progress;
179
+ document.getElementById('reset-button').disabled = data.requests_in_progress;
180
+ })
181
+ .catch(error => {
182
+ console.error('Erreur lors de la récupération de la progression:', error);
183
+ });
184
+ }
185
+
186
+ function updateRequests() {
187
+ fetch('/progress')
188
+ .then(response => response.json())
189
+ .then(data => {
190
+ const tableBody = document.getElementById('requests-table');
191
+ tableBody.innerHTML = '';
192
+
193
+ if (data.requests.length === 0) {
194
+ tableBody.innerHTML = '<tr><td colspan="6" class="text-center">Aucune requête effectuée.</td></tr>';
195
+ return;
196
  }
 
197
 
198
+ data.requests.forEach((req, index) => {
199
+ const row = document.createElement('tr');
200
+ const statusClass = `status-${req.status || 'pending'}`;
201
+
202
+ row.innerHTML = `
203
+ <td>${index + 1}</td>
204
+ <td>${req.url || 'N/A'}</td>
205
+ <td><span class="status-badge ${statusClass}">${req.status || 'pending'}</span></td>
206
+ <td>${req.status_code || '-'}</td>
207
+ <td>${req.response_time || '-'}</td>
208
+ <td>${req.end_time || req.start_time || '-'}</td>
209
+ `;
210
+
211
+ tableBody.appendChild(row);
212
+ });
213
+ })
214
+ .catch(error => {
215
+ console.error('Erreur lors de la récupération des requêtes:', error);
216
+ });
217
+ }
218
+
219
+ function updateStats() {
220
+ fetch('/stats')
221
+ .then(response => response.json())
222
+ .then(data => {
223
+ const statsContainer = document.getElementById('stats-container');
224
 
225
+ let statusCodeHtml = '<div class="mt-3"><h5>Codes de statut</h5>';
226
+ if (Object.keys(data.status_codes).length > 0) {
227
+ statusCodeHtml += '<div class="row">';
228
+ for (const [code, count] of Object.entries(data.status_codes)) {
229
+ const colorClass = code.startsWith('2') ? 'success' :
230
+ code.startsWith('3') ? 'info' :
231
+ code.startsWith('4') ? 'warning' : 'danger';
232
+ statusCodeHtml += `
233
+ <div class="col-md-3 mb-2">
234
+ <div class="card bg-${colorClass} text-white">
235
+ <div class="card-body p-2 text-center">
236
+ <h5 class="card-title">${code}</h5>
237
+ <p class="card-text">${count} requêtes</p>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ `;
242
+ }
243
+ statusCodeHtml += '</div>';
244
+ } else {
245
+ statusCodeHtml += '<p>Aucun code de statut disponible.</p>';
246
+ }
247
+ statusCodeHtml += '</div>';
248
 
249
+ let errorTypesHtml = '<div class="mt-3"><h5>Types d\'erreurs</h5>';
250
+ if (Object.keys(data.error_types).length > 0) {
251
+ errorTypesHtml += '<ul class="list-group">';
252
+ for (const [type, count] of Object.entries(data.error_types)) {
253
+ errorTypesHtml += `
254
+ <li class="list-group-item d-flex justify-content-between align-items-center">
255
+ ${type}
256
+ <span class="badge bg-danger rounded-pill">${count}</span>
257
+ </li>
258
+ `;
259
+ }
260
+ errorTypesHtml += '</ul>';
261
  } else {
262
+ errorTypesHtml += '<p>Aucune erreur disponible.</p>';
 
263
  }
264
+ errorTypesHtml += '</div>';
265
+
266
+ statsContainer.innerHTML = `
267
+ <div class="row">
268
+ <div class="col-md-6">
269
+ <h5>Résumé</h5>
270
+ <ul class="list-group">
271
+ <li class="list-group-item d-flex justify-content-between align-items-center">
272
+ Requêtes totales
273
+ <span class="badge bg-primary rounded-pill">${data.total_requests}</span>
274
+ </li>
275
+ <li class="list-group-item d-flex justify-content-between align-items-center">
276
+ Requêtes réussies
277
+ <span class="badge bg-success rounded-pill">${data.successful_requests}</span>
278
+ </li>
279
+ <li class="list-group-item d-flex justify-content-between align-items-center">
280
+ Requêtes échouées
281
+ <span class="badge bg-danger rounded-pill">${data.failed_requests}</span>
282
+ </li>
283
+ </ul>
284
+ </div>
285
+ <div class="col-md-6">
286
+ <h5>Performance</h5>
287
+ <div class="card">
288
+ <div class="card-body">
289
+ <h3>${data.avg_response_time.toFixed(3)} s</h3>
290
+ <p class="text-muted">Temps de réponse moyen</p>
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ ${statusCodeHtml}
296
+ ${errorTypesHtml}
297
+ `;
298
+ })
299
+ .catch(error => {
300
+ console.error('Erreur lors de la récupération des statistiques:', error);
301
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  }
303
+ </script>
304
+ </body>
305
+ </html>