Attaque2 / templates /index.html
Docfile's picture
Create templates/index.html
9faa19a verified
raw
history blame
30.3 kB
<!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 Comptes GabaoHub</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
<style>
body {
padding-top: 2rem;
background-color: #f8f9fa;
}
.card {
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 10px;
overflow: hidden;
}
.card-header {
background-color: #4c5daf;
color: white;
padding: 1rem;
}
.success {
background-color: #d4edda;
}
.failed {
background-color: #f8d7da;
}
.pending {
background-color: #fff3cd;
}
.account-details {
font-family: monospace;
font-size: 0.9rem;
}
.progress {
height: 25px;
border-radius: 15px;
}
#status-badge {
font-size: 1rem;
}
.settings-panel {
background-color: #f1f3f9;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
.copy-btn {
cursor: pointer;
}
.export-options {
margin-bottom: 15px;
}
.stats-card {
background-color: #e9ecef;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
transition: all 0.3s ease;
}
.stats-card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
}
.stats-value {
font-size: 1.5rem;
font-weight: bold;
}
.table-responsive {
border-radius: 8px;
overflow: hidden;
}
/* Animation pour les nouveaux comptes créés */
@keyframes highlight {
0% { background-color: #fff9c4; }
100% { background-color: transparent; }
}
.highlight-new {
animation: highlight 2s ease-in-out;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2><i class="bi bi-person-plus-fill"></i> Générateur de Comptes GabaoHub</h2>
<span id="status-badge" class="badge {% if creation_in_progress %}bg-warning{% else %}bg-success{% endif %}">
{% if creation_in_progress %}
<i class="bi bi-hourglass-split"></i> En cours
{% else %}
<i class="bi bi-check-circle"></i> Prêt
{% endif %}
</span>
</div>
<div class="card-body">
<div class="settings-panel mb-4">
<h5><i class="bi bi-gear-fill"></i> Paramètres de création</h5>
<form action="/start" method="post" class="mb-3" id="creation-form">
<div class="row g-3">
<div class="col-md-6">
<label for="num_accounts" class="form-label">Nombre de comptes</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-people-fill"></i></span>
<input type="number" class="form-control" id="num_accounts" name="num_accounts" value="100" min="1" max="100000" {% if creation_in_progress %}disabled{% endif %}>
</div>
</div>
<div class="col-md-6">
<label for="concurrency" class="form-label">Concurrence</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-speedometer"></i></span>
<input type="number" class="form-control" id="concurrency" name="concurrency" value="5" min="1" max="10" {% if creation_in_progress %}disabled{% endif %}>
<button type="submit" class="btn btn-primary" {% if creation_in_progress %}disabled{% endif %}>
<i class="bi bi-play-fill"></i> Démarrer
</button>
</div>
<small class="text-muted">Nombre de tâches simultanées (1-10)</small>
</div>
</div>
</form>
</div>
{% if creation_in_progress or progress > 0 %}
<div class="mb-4">
<h5><i class="bi bi-graph-up"></i> Progression</h5>
<div class="progress mb-2">
<div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated bg-primary"
role="progressbar" style="width: {{ (progress / total * 100) if total > 0 else 0 }}%;"
aria-valuenow="{{ progress }}" aria-valuemin="0" aria-valuemax="{{ total }}">
{{ progress }}/{{ total }}
</div>
</div>
<p id="progress-text" class="text-muted">
<i class="bi bi-info-circle"></i> {{ progress }} compte(s) sur {{ total }} créé(s)
{% if creation_in_progress %}
<span class="spinner-border spinner-border-sm text-primary ms-2" role="status"></span>
{% endif %}
</p>
</div>
<div class="row mb-4">
<div class="col-md-4">
<div class="stats-card text-center">
<i class="bi bi-check-circle-fill text-success mb-2" style="font-size: 1.5rem;"></i>
<h6>Réussis</h6>
<div id="success-count" class="stats-value text-success">{{ accounts|selectattr('status', 'equalto', 'success')|list|length }}</div>
</div>
</div>
<div class="col-md-4">
<div class="stats-card text-center">
<i class="bi bi-x-circle-fill text-danger mb-2" style="font-size: 1.5rem;"></i>
<h6>Échoués</h6>
<div id="failed-count" class="stats-value text-danger">{{ accounts|selectattr('status', 'equalto', 'failed')|list|length }}</div>
</div>
</div>
<div class="col-md-4">
<div class="stats-card text-center">
<i class="bi bi-hourglass-split text-warning mb-2" style="font-size: 1.5rem;"></i>
<h6>En attente</h6>
<div id="pending-count" class="stats-value text-warning">{{ accounts|selectattr('status', 'equalto', 'pending')|list|length }}</div>
</div>
</div>
</div>
{% endif %}
{% if accounts and accounts|length > 0 %}
<div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5><i class="bi bi-person-lines-fill"></i> Comptes créés</h5>
<div class="d-flex">
<div class="export-options me-2">
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="exportDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-download"></i> Exporter
</button>
<ul class="dropdown-menu" aria-labelledby="exportDropdown">
<li><a class="dropdown-item" href="#" id="export-all"><i class="bi bi-file-earmark-text"></i> Tous les comptes</a></li>
<li><a class="dropdown-item" href="#" id="export-success"><i class="bi bi-file-earmark-check"></i> Comptes réussis</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" id="copy-table"><i class="bi bi-clipboard"></i> Copier le tableau</a></li>
</ul>
</div>
</div>
<form action="/reset" method="post" onsubmit="return confirm('Êtes-vous sûr de vouloir supprimer toutes les données?');">
<button type="submit" class="btn btn-danger" {% if creation_in_progress %}disabled{% endif %}>
<i class="bi bi-trash"></i> Réinitialiser
</button>
</form>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover" id="accounts-table">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Nom d'utilisateur</th>
<th>Mot de passe</th>
<th>Province</th>
<th>Ville</th>
<th>Statut</th>
<th>Horodatage</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="accounts-table-body">
{% for account in accounts %}
<tr class="{{ account.status }}">
<td>{{ loop.index }}</td>
<td>{{ account.username }}</td>
<td>
<span class="password-text">{{ account.password }}</span>
<i class="bi bi-clipboard copy-btn ms-2" data-clipboard-text="{{ account.password }}" title="Copier le mot de passe"></i>
</td>
<td>{{ account.province }}</td>
<td>{{ account.ville }}</td>
<td>
<span class="badge {% if account.status == 'success' %}bg-success{% elif account.status == 'failed' %}bg-danger{% else %}bg-warning{% endif %}">
{% if account.status == 'success' %}
<i class="bi bi-check-circle-fill"></i>
{% elif account.status == 'failed' %}
<i class="bi bi-x-circle-fill"></i>
{% else %}
<i class="bi bi-hourglass-split"></i>
{% endif %}
{{ account.status }}
</span>
</td>
<td>{{ account.timestamp if account.timestamp else 'N/A' }}</td>
<td>
<button class="btn btn-sm btn-outline-primary copy-account" data-username="{{ account.username }}" data-password="{{ account.password }}">
<i class="bi bi-clipboard-check"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<div class="alert alert-info">
<i class="bi bi-info-circle-fill"></i> Aucun compte n'a encore été créé. Cliquez sur "Démarrer" pour commencer.
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Toast pour les notifications -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
<div id="copyToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header bg-success text-white">
<i class="bi bi-check-circle-fill me-2"></i>
<strong class="me-auto">Notification</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body" id="toast-message">
Copié avec succès!
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Toast pour afficher les notifications
function showToast(message, type = 'success') {
const toast = $('#copyToast');
const toastHeader = toast.find('.toast-header');
// Définir le type de toast
if (type === 'success') {
toastHeader.removeClass('bg-danger').addClass('bg-success');
toastHeader.find('i').removeClass('bi-x-circle-fill').addClass('bi-check-circle-fill');
} else {
toastHeader.removeClass('bg-success').addClass('bg-danger');
toastHeader.find('i').removeClass('bi-check-circle-fill').addClass('bi-x-circle-fill');
}
// Mettre à jour le message
$('#toast-message').text(message);
// Afficher le toast
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
}
// Fonction pour copier du texte dans le presse-papiers
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
showToast('Copié avec succès!');
}, function() {
showToast('Échec de la copie', 'error');
});
}
// Fonction pour exporter les données en CSV
function exportToCSV(accounts, onlySuccess = false) {
if (!accounts || accounts.length === 0) return;
// Filtrer les comptes si nécessaire
const dataToExport = onlySuccess ? accounts.filter(a => a.status === 'success') : accounts;
// Créer les en-têtes CSV
let csv = 'Username,Password,Province,Ville,Status,Timestamp\n';
// Ajouter les données
dataToExport.forEach(account => {
const username = account.username || '';
const password = account.password || '';
const province = account.province || '';
const ville = account.ville || '';
const status = account.status || '';
const timestamp = account.timestamp || '';
csv += `"${username}","${password}","${province}","${ville}","${status}","${timestamp}"\n`;
});
// Créer un blob et télécharger
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', onlySuccess ? 'gabao_hub_success_accounts.csv' : 'gabao_hub_all_accounts.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Fonction pour mettre à jour les informations sur la page
function updateProgress() {
if ({{ creation_in_progress|tojson }}) {
$.ajax({
url: '/progress',
type: 'GET',
dataType: 'json',
success: function(data) {
// Mettre à jour l'état
if (data.creation_in_progress) {
$('#status-badge').html('<i class="bi bi-hourglass-split"></i> En cours').removeClass('bg-success').addClass('bg-warning');
$('#creation-form button').prop('disabled', true);
$('#num_accounts').prop('disabled', true);
$('#concurrency').prop('disabled', true);
$('form[action="/reset"] button').prop('disabled', true);
} else {
$('#status-badge').html('<i class="bi bi-check-circle"></i> Prêt').removeClass('bg-warning').addClass('bg-success');
$('#creation-form button').prop('disabled', false);
$('#num_accounts').prop('disabled', false);
$('#concurrency').prop('disabled', false);
$('form[action="/reset"] button').prop('disabled', false);
}
// Mettre à jour la barre de progression
let percentage = data.total > 0 ? (data.progress / data.total * 100) : 0;
$('#progress-bar').css('width', percentage + '%').attr('aria-valuenow', data.progress);
$('#progress-bar').text(data.progress + '/' + data.total);
let progressText = data.progress + ' compte(s) sur ' + data.total + ' créé(s)';
if (data.creation_in_progress) {
progressText += ' <span class="spinner-border spinner-border-sm text-primary ms-2" role="status"></span>';
}
$('#progress-text').html('<i class="bi bi-info-circle"></i> ' + progressText);
// Mettre à jour les compteurs statistiques
updateStatCounters(data.accounts);
// Mettre à jour le tableau des comptes
updateAccountsTable(data.accounts);
// Continuer la mise à jour uniquement si la création est en cours
if (data.creation_in_progress) {
setTimeout(updateProgress, 2000);
} else {
// Rechargement de la page pour afficher les résultats finaux
location.reload();
}
}
});
}
}
// Fonction pour mettre à jour les compteurs statistiques
function updateStatCounters(accounts) {
if (!accounts || accounts.length === 0) return;
const successCount = accounts.filter(a => a.status === 'success').length;
const failedCount = accounts.filter(a => a.status === 'failed').length;
const pendingCount = accounts.filter(a => a.status === 'pending').length;
$('#success-count').text(successCount);
$('#failed-count').text(failedCount);
$('#pending-count').text(pendingCount);
}
// Fonction pour mettre à jour le tableau des comptes
function updateAccountsTable(accounts) {
if (!accounts || accounts.length === 0) return;
let tableBody = $('#accounts-table-body');
let existingRows = tableBody.find('tr').length;
// Parcourir les comptes et mettre à jour le tableau
accounts.forEach(function(account, index) {
let statusClass = '';
let statusIcon = '';
let statusBadge = '';
if (account.status === 'success') {
statusClass = 'success';
statusIcon = '<i class="bi bi-check-circle-fill"></i>';
statusBadge = '<span class="badge bg-success">' + statusIcon + ' success</span>';
} else if (account.status === 'failed') {
statusClass = 'failed';
statusIcon = '<i class="bi bi-x-circle-fill"></i>';
statusBadge = '<span class="badge bg-danger">' + statusIcon + ' failed</span>';
} else {
statusClass = 'pending';
statusIcon = '<i class="bi bi-hourglass-split"></i>';
statusBadge = '<span class="badge bg-warning">' + statusIcon + ' pending</span>';
}
const timestamp = account.timestamp ? account.timestamp : 'N/A';
const username = account.username || '';
const password = account.password || '';
const province = account.province || '';
const ville = account.ville || '';
// Vérifier si cette ligne existe déjà
let existingRow = tableBody.find('tr').eq(index);
if (existingRow.length) {
// Mettre à jour la ligne existante
existingRow.attr('class', statusClass);
existingRow.find('td').eq(1).text(username);
let passwordCell = existingRow.find('td').eq(2);
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>');
existingRow.find('td').eq(3).text(province);
existingRow.find('td').eq(4).text(ville);
existingRow.find('td').eq(5).html(statusBadge);
existingRow.find('td').eq(6).text(timestamp);
// Mettre à jour le bouton de copie
let actionsCell = existingRow.find('td').eq(7);
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>');
// Ajouter animation si le statut a changé de "pending"
if (existingRow.data('status') === 'pending' && account.status !== 'pending') {
existingRow.addClass('highlight-new');
setTimeout(function() {
existingRow.removeClass('highlight-new');
}, 2000);
}
// Stocker le statut actuel
existingRow.data('status', account.status);
} else {
// Ajouter une nouvelle ligne
let newRow = $(`
<tr class="${statusClass}" data-status="${account.status}">
<td>${index + 1}</td>
<td>${username}</td>
<td>
<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>
</td>
<td>${province}</td>
<td>${ville}</td>
<td>${statusBadge}</td>
<td>${timestamp}</td>
<td>
<button class="btn btn-sm btn-outline-primary copy-account" data-username="${username}" data-password="${password}">
<i class="bi bi-clipboard-check"></i>
</button>
</td>
</tr>
`);
tableBody.append(newRow);
newRow.addClass('highlight-new');
setTimeout(function() {
newRow.removeClass('highlight-new');
}, 2000);
}
});
}
// Lancer la mise à jour de la progression au chargement de la page
$(document).ready(function() {
// Initialiser les gestionnaires d'événements
// Copier le mot de passe
$(document).on('click', '.copy-btn', function() {
const text = $(this).data('clipboard-text');
copyToClipboard(text);
});
// Copier les informations complètes du compte
$(document).on('click', '.copy-account', function() {
const username = $(this).data('username');
const password = $(this).data('password');
const accountInfo = `Nom d'utilisateur: ${username}\nMot de passe: ${password}`;
copyToClipboard(accountInfo);
});
// Exporter tous les comptes
$('#export-all').click(function(e) {
e.preventDefault();
$.ajax({
url: '/progress',
type: 'GET',
dataType: 'json',
success: function(data) {
exportToCSV(data.accounts, false);
}
});
});
// Exporter uniquement les comptes réussis
$('#export-success').click(function(e) {
e.preventDefault();
$.ajax({
url: '/progress',
type: 'GET',
dataType: 'json',
success: function(data) {
exportToCSV(data.accounts, true);
}
});
});
// Copier le tableau entier
$('#copy-table').click(function(e) {
e.preventDefault();
// Construire un tableau formaté pour le presse-papiers
let tableText = "Index\tNom d'utilisateur\tMot de passe\tProvince\tVille\tStatut\tHorodatage\n";
$('#accounts-table tbody tr').each(function() {
const row = $(this);
const index = row.find('td').eq(0).text();
const username = row.find('td').eq(1).text();
const password = row.find('.password-text').text();
const province = row.find('td').eq(3).text();
const ville = row.find('td').eq(4).text();
const status = row.hasClass('success') ? 'success' : (row.hasClass('failed') ? 'failed' : 'pending');
const timestamp = row.find('td').eq(6).text();
tableText += `${index}\t${username}\t${password}\t${province}\t${ville}\t${status}\t${timestamp}\n`;
});
copyToClipboard(tableText);
});
// Lancer la mise à jour si création en cours
if ({{ creation_in_progress|tojson }}) {
setTimeout(updateProgress, 1000);
}
});
</script>
</body>
</html>