Files
wevads-platform/public/warmup-system.php
2026-04-07 03:04:16 +02:00

370 lines
22 KiB
PHP

<?php
require_once('/opt/wevads/config/credentials.php');
error_reporting(E_ALL);
ini_set('display_errors', 0);
function getDB() {
static $pdo = null;
if ($pdo === null) {
$pdo = get_pdo('adx_system');
}
return $pdo;
}
function ensureTables() {
$pdo = getDB();
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.warmup_accounts (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255),
smtp_host VARCHAR(255),
smtp_port INTEGER DEFAULT 587,
imap_host VARCHAR(255),
imap_port INTEGER DEFAULT 993,
encryption VARCHAR(20) DEFAULT 'tls',
provider VARCHAR(100),
daily_limit INTEGER DEFAULT 50,
current_day INTEGER DEFAULT 1,
emails_sent_today INTEGER DEFAULT 0,
total_sent INTEGER DEFAULT 0,
reputation_score INTEGER DEFAULT 50,
status VARCHAR(50) DEFAULT 'active',
last_sent TIMESTAMP,
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.warmup_schedules (
id SERIAL PRIMARY KEY,
day INTEGER UNIQUE,
emails_count INTEGER
)");
$count = $pdo->query("SELECT COUNT(*) FROM admin.warmup_schedules")->fetchColumn();
if ($count == 0) {
$pdo->exec("INSERT INTO admin.warmup_schedules (day, emails_count) VALUES
(1,5),(2,8),(3,12),(4,18),(5,25),(6,35),(7,50),(14,100),(21,200),(30,350),(45,500),(60,750),(90,1000)");
}
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.warmup_messages (
id SERIAL PRIMARY KEY,
subject VARCHAR(500) NOT NULL,
body TEXT NOT NULL,
category VARCHAR(100) DEFAULT 'general',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$msgCount = $pdo->query("SELECT COUNT(*) FROM admin.warmup_messages")->fetchColumn();
if ($msgCount == 0) {
$pdo->exec("INSERT INTO admin.warmup_messages (subject, body, category) VALUES
('Meeting follow-up', 'Hi, Just following up on our conversation. Best regards', 'business'),
('Quick question', 'Hello, I had a quick question about the project. Thanks', 'business'),
('Thank you', 'Hi there, Thank you for your help! Best', 'personal'),
('Checking in', 'Hey, Just wanted to check in. Hope all is well!', 'personal')");
}
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.warmup_logs (
id SERIAL PRIMARY KEY,
from_account_id INTEGER,
to_account_id INTEGER,
from_email VARCHAR(255),
to_email VARCHAR(255),
subject VARCHAR(500),
status VARCHAR(50) DEFAULT 'sent',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.warmup_pools (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
accounts_count INTEGER DEFAULT 0,
status VARCHAR(50) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
}
if (isset($_GET['action']) || isset($_POST['action'])) {
header('Content-Type: application/json');
ensureTables();
$action = $_GET['action'] ?? $_POST['action'];
$pdo = getDB();
try {
switch ($action) {
case 'stats':
echo json_encode(['success' => true, 'stats' => [
'total' => $pdo->query("SELECT COUNT(*) FROM admin.warmup_accounts")->fetchColumn(),
'active' => $pdo->query("SELECT COUNT(*) FROM admin.warmup_accounts WHERE status = 'active'")->fetchColumn(),
'today' => $pdo->query("SELECT COALESCE(SUM(emails_sent_today), 0) FROM admin.warmup_accounts")->fetchColumn(),
'total_sent' => $pdo->query("SELECT COALESCE(SUM(total_sent), 0) FROM admin.warmup_accounts")->fetchColumn(),
'avg_rep' => round($pdo->query("SELECT COALESCE(AVG(reputation_score), 50) FROM admin.warmup_accounts")->fetchColumn()),
'pools' => $pdo->query("SELECT COUNT(*) FROM admin.warmup_pools")->fetchColumn(),
]]);
break;
case 'list_accounts':
$accounts = $pdo->query("SELECT * FROM admin.warmup_accounts ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'accounts' => $accounts]);
break;
case 'add_account':
$stmt = $pdo->prepare("INSERT INTO admin.warmup_accounts (email, password, smtp_host, smtp_port, imap_host, imap_port, provider) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (email) DO NOTHING RETURNING id");
$stmt->execute([$_POST['email'], $_POST['password'], $_POST['smtp_host'], $_POST['smtp_port'] ?? 587, $_POST['imap_host'], $_POST['imap_port'] ?? 993, $_POST['provider'] ?? 'custom']);
echo json_encode(['success' => true, 'id' => $stmt->fetchColumn()]);
break;
case 'delete_account':
$pdo->exec("DELETE FROM admin.warmup_accounts WHERE id = " . intval($_POST['id']));
echo json_encode(['success' => true]);
break;
case 'get_schedule':
$schedule = $pdo->query("SELECT * FROM admin.warmup_schedules ORDER BY day")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'schedule' => $schedule]);
break;
case 'list_messages':
$messages = $pdo->query("SELECT * FROM admin.warmup_messages ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'messages' => $messages]);
break;
case 'add_message':
$stmt = $pdo->prepare("INSERT INTO admin.warmup_messages (subject, body, category) VALUES (?, ?, ?)");
$stmt->execute([$_POST['subject'], $_POST['body'], $_POST['category'] ?? 'general']);
echo json_encode(['success' => true]);
break;
case 'delete_message':
$pdo->exec("DELETE FROM admin.warmup_messages WHERE id = " . intval($_POST['id']));
echo json_encode(['success' => true]);
break;
case 'list_pools':
$pools = $pdo->query("SELECT * FROM admin.warmup_pools ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'pools' => $pools]);
break;
case 'create_pool':
$stmt = $pdo->prepare("INSERT INTO admin.warmup_pools (name, description) VALUES (?, ?) RETURNING id");
$stmt->execute([$_POST['name'], $_POST['description'] ?? '']);
echo json_encode(['success' => true, 'id' => $stmt->fetchColumn()]);
break;
case 'get_logs':
$logs = $pdo->query("SELECT * FROM admin.warmup_logs ORDER BY created_at DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'logs' => $logs]);
break;
case 'reset_daily':
$pdo->exec("UPDATE admin.warmup_accounts SET current_day = current_day + 1, emails_sent_today = 0 WHERE status = 'active'");
echo json_encode(['success' => true]);
break;
default:
echo json_encode(['success' => false, 'error' => 'Unknown action']);
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
exit;
}
ensureTables();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Warmup System</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root{--bg:#0a0a0f;--bg2:#12121a;--bg3:#1a1a25;--primary:#6366f1;--success:#10b981;--warning:#f59e0b;--danger:#ef4444;--text:#e2e8f0;--text2:#94a3b8;--border:#2a2a3a;--fire:#ff6b35}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
.header{background:linear-gradient(135deg,#1a0a0f,#2a1a1f);border-bottom:1px solid var(--border);padding:1rem 2rem;display:flex;justify-content:space-between;align-items:center}
.header h1{font-size:1.3rem;display:flex;align-items:center;gap:.5rem;color:var(--fire)}
.badge-v{background:linear-gradient(135deg,var(--fire),#ff8c42);padding:.25rem .75rem;border-radius:15px;font-size:.7rem;font-weight:700}
.container{max-width:1400px;margin:0 auto;padding:1.5rem}
.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:1rem;margin-bottom:1.5rem}
.stat{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:1rem;text-align:center}
.stat .v{font-size:1.6rem;font-weight:700;color:var(--fire)}.stat .l{color:var(--text2);font-size:.7rem}
.tabs{display:flex;gap:.5rem;margin-bottom:1.5rem}
.tab{padding:.6rem 1.2rem;background:var(--bg2);border:1px solid var(--border);border-radius:8px;cursor:pointer;font-size:.85rem;transition:all .2s}
.tab:hover{border-color:var(--fire)}.tab.active{background:linear-gradient(135deg,var(--fire),#ff8c42);border-color:transparent;color:white}
.panel{display:none}.panel.active{display:block}
.card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:1.25rem;margin-bottom:1rem}
.card h3{margin-bottom:1rem;font-size:1rem;display:flex;align-items:center;gap:.5rem}
.form-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.75rem;margin-bottom:1rem}
.form-group{display:flex;flex-direction:column;gap:.25rem}
.form-group label{font-size:.7rem;color:var(--text2)}
.form-group input,.form-group select,.form-group textarea{padding:.5rem .7rem;background:var(--bg3);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.8rem}
.form-group textarea{min-height:80px}
.btn{padding:.5rem 1rem;border:none;border-radius:6px;font-weight:600;cursor:pointer;transition:all .2s;display:inline-flex;align-items:center;gap:.4rem;font-size:.8rem}
.btn:hover{transform:translateY(-1px)}.btn-fire{background:linear-gradient(135deg,var(--fire),#ff8c42);color:white}
.btn-primary{background:var(--primary);color:white}.btn-success{background:var(--success);color:white}.btn-danger{background:var(--danger);color:white}
.btn-sm{padding:.35rem .6rem;font-size:.75rem}
table{width:100%;border-collapse:collapse;font-size:.8rem}
th,td{padding:.5rem;text-align:left;border-bottom:1px solid var(--border)}
th{color:var(--text2);font-weight:500;font-size:.7rem}
.badge{padding:.2rem .5rem;border-radius:10px;font-size:.65rem;font-weight:600}
.badge-active{background:rgba(16,185,129,.15);color:var(--success)}
.badge-paused{background:rgba(245,158,11,.15);color:var(--warning)}
.reputation{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.8rem}
.rep-good{background:rgba(16,185,129,.2);color:var(--success);border:2px solid var(--success)}
.rep-medium{background:rgba(245,158,11,.2);color:var(--warning);border:2px solid var(--warning)}
.rep-bad{background:rgba(239,68,68,.2);color:var(--danger);border:2px solid var(--danger)}
.progress{height:6px;background:var(--bg3);border-radius:3px;overflow:hidden}.progress-fill{height:100%;background:linear-gradient(90deg,var(--fire),#ff8c42);border-radius:3px}
.schedule-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:.5rem}
.schedule-item{background:var(--bg3);border-radius:6px;padding:.5rem;text-align:center}
.schedule-item .day{font-size:.65rem;color:var(--text2)}.schedule-item .count{font-size:1rem;font-weight:700;color:var(--fire)}
.empty{text-align:center;padding:2rem;color:var(--text2)}
.action-bar{display:flex;gap:.75rem;margin-bottom:1rem}
.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:100;align-items:center;justify-content:center}
.modal.active{display:flex}
.modal-content{background:var(--bg2);border-radius:12px;padding:1.5rem;width:500px;max-width:90%}
.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem}
.modal-close{background:none;border:none;color:var(--text2);font-size:1.2rem;cursor:pointer}
</style>
</head>
<body>
<div class="header"><h1><i class="fas fa-fire"></i> Warmup System</h1><span class="badge-v">v2.0</span></div>
<div class="container">
<div class="stats">
<div class="stat"><div class="v" id="sTotal">0</div><div class="l">Total</div></div>
<div class="stat"><div class="v" id="sActive">0</div><div class="l">Active</div></div>
<div class="stat"><div class="v" id="sToday">0</div><div class="l">Today</div></div>
<div class="stat"><div class="v" id="sSent">0</div><div class="l">Total Sent</div></div>
<div class="stat"><div class="v" id="sRep">0</div><div class="l">Avg Rep</div></div>
<div class="stat"><div class="v" id="sPools">0</div><div class="l">Pools</div></div>
</div>
<div class="tabs">
<div class="tab active" data-tab="accounts"><i class="fas fa-users"></i> Accounts</div>
<div class="tab" data-tab="schedule"><i class="fas fa-calendar"></i> Schedule</div>
<div class="tab" data-tab="messages"><i class="fas fa-envelope"></i> Messages</div>
<div class="tab" data-tab="pools"><i class="fas fa-layer-group"></i> Pools</div>
<div class="tab" data-tab="logs"><i class="fas fa-history"></i> Logs</div>
</div>
<div class="panel active" id="panel-accounts">
<div class="action-bar">
<button class="btn btn-fire" onclick="showAddAccount()"><i class="fas fa-plus"></i> Add Account</button>
<button class="btn btn-warning" onclick="resetDaily()"><i class="fas fa-redo"></i> Reset Daily</button>
</div>
<div class="card">
<h3><i class="fas fa-fire"></i> Warmup Accounts</h3>
<table>
<thead><tr><th>Email</th><th>Provider</th><th>Day</th><th>Today</th><th>Progress</th><th>Rep</th><th>Status</th><th>Actions</th></tr></thead>
<tbody id="accountsTable"></tbody>
</table>
</div>
</div>
<div class="panel" id="panel-schedule">
<div class="card">
<h3><i class="fas fa-calendar"></i> Warmup Schedule</h3>
<div class="schedule-grid" id="scheduleGrid"></div>
</div>
</div>
<div class="panel" id="panel-messages">
<div class="card">
<h3><i class="fas fa-plus"></i> Add Template</h3>
<div class="form-row">
<div class="form-group"><label>Subject</label><input type="text" id="msgSubject"></div>
<div class="form-group"><label>Category</label><select id="msgCategory"><option value="business">Business</option><option value="personal">Personal</option></select></div>
</div>
<div class="form-group"><label>Body</label><textarea id="msgBody"></textarea></div>
<button class="btn btn-fire" onclick="addMessage()"><i class="fas fa-plus"></i> Add</button>
</div>
<div class="card">
<h3><i class="fas fa-envelope"></i> Templates</h3>
<table><thead><tr><th>Subject</th><th>Category</th><th>Actions</th></tr></thead><tbody id="messagesTable"></tbody></table>
</div>
</div>
<div class="panel" id="panel-pools">
<div class="card">
<h3><i class="fas fa-plus"></i> Create Pool</h3>
<div class="form-row">
<div class="form-group"><label>Name</label><input type="text" id="poolName"></div>
<div class="form-group"><label>Description</label><input type="text" id="poolDesc"></div>
<div class="form-group"><label>&nbsp;</label><button class="btn btn-fire" onclick="createPool()"><i class="fas fa-plus"></i> Create</button></div>
</div>
</div>
<div class="card">
<h3><i class="fas fa-layer-group"></i> Pools</h3>
<table><thead><tr><th>Name</th><th>Description</th><th>Members</th><th>Status</th></tr></thead><tbody id="poolsTable"></tbody></table>
</div>
</div>
<div class="panel" id="panel-logs">
<div class="card">
<h3><i class="fas fa-history"></i> Recent Logs</h3>
<div id="logsContainer"></div>
</div>
</div>
</div>
<div class="modal" id="addAccountModal">
<div class="modal-content">
<div class="modal-header"><h3>Add Account</h3><button class="modal-close" onclick="closeModal()">&times;</button></div>
<div class="form-row">
<div class="form-group"><label>Email</label><input type="email" id="accEmail"></div>
<div class="form-group"><label>Password</label><input type="password" id="accPassword"></div>
</div>
<div class="form-row">
<div class="form-group"><label>SMTP Host</label><input type="text" id="accSmtpHost" value="smtp.gmail.com"></div>
<div class="form-group"><label>SMTP Port</label><input type="number" id="accSmtpPort" value="587"></div>
</div>
<div class="form-row">
<div class="form-group"><label>IMAP Host</label><input type="text" id="accImapHost" value="imap.gmail.com"></div>
<div class="form-group"><label>IMAP Port</label><input type="number" id="accImapPort" value="993"></div>
</div>
<div class="form-group"><label>Provider</label><select id="accProvider"><option value="gmail">Gmail</option><option value="outlook">Outlook</option><option value="custom">Custom</option></select></div>
<button class="btn btn-fire" onclick="addAccount()" style="margin-top:1rem"><i class="fas fa-plus"></i> Add</button>
</div>
</div>
<script>
document.querySelectorAll('.tab').forEach(t=>{t.onclick=()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));t.classList.add('active');document.getElementById('panel-'+t.dataset.tab).classList.add('active');if(t.dataset.tab==='schedule')loadSchedule();if(t.dataset.tab==='messages')loadMessages();if(t.dataset.tab==='pools')loadPools();if(t.dataset.tab==='logs')loadLogs();}});
function loadStats(){fetch('?action=stats').then(r=>r.json()).then(d=>{if(d.success){document.getElementById('sTotal').textContent=d.stats.total;document.getElementById('sActive').textContent=d.stats.active;document.getElementById('sToday').textContent=d.stats.today;document.getElementById('sSent').textContent=Number(d.stats.total_sent).toLocaleString();document.getElementById('sRep').textContent=d.stats.avg_rep;document.getElementById('sPools').textContent=d.stats.pools;}});}
function loadAccounts(){fetch('?action=list_accounts').then(r=>r.json()).then(d=>{if(d.success){document.getElementById('accountsTable').innerHTML=d.accounts.length?d.accounts.map(a=>{const repClass=a.reputation_score>=70?'rep-good':(a.reputation_score>=40?'rep-medium':'rep-bad');const prog=Math.min(100,(a.current_day/90)*100);return`<tr><td><strong>${a.email}</strong></td><td>${a.provider||'custom'}</td><td>Day ${a.current_day}</td><td>${a.emails_sent_today}/${a.daily_limit||50}</td><td style="width:80px"><div class="progress"><div class="progress-fill" style="width:${prog}%"></div></div></td><td><div class="reputation ${repClass}">${a.reputation_score}</div></td><td><span class="badge badge-${a.status}">${a.status}</span></td><td><button class="btn btn-danger btn-sm" onclick="deleteAccount(${a.id})"><i class="fas fa-trash"></i></button></td></tr>`;}).join(''):'<tr><td colspan="8" class="empty">No accounts</td></tr>';}});}
function loadSchedule(){fetch('?action=get_schedule').then(r=>r.json()).then(d=>{if(d.success){document.getElementById('scheduleGrid').innerHTML=d.schedule.map(s=>`<div class="schedule-item"><div class="day">Day ${s.day}</div><div class="count">${s.emails_count}</div></div>`).join('');}});}
function loadMessages(){fetch('?action=list_messages').then(r=>r.json()).then(d=>{if(d.success){document.getElementById('messagesTable').innerHTML=d.messages.map(m=>`<tr><td>${m.subject}</td><td>${m.category}</td><td><button class="btn btn-danger btn-sm" onclick="deleteMessage(${m.id})"><i class="fas fa-trash"></i></button></td></tr>`).join('');}});}
function loadPools(){fetch('?action=list_pools').then(r=>r.json()).then(d=>{if(d.success){document.getElementById('poolsTable').innerHTML=d.pools.length?d.pools.map(p=>`<tr><td>${p.name}</td><td>${p.description||'-'}</td><td>${p.accounts_count}</td><td><span class="badge badge-${p.status}">${p.status}</span></td></tr>`).join(''):'<tr><td colspan="4" class="empty">No pools</td></tr>';}});}
function loadLogs(){fetch('?action=get_logs').then(r=>r.json()).then(d=>{if(d.success){document.getElementById('logsContainer').innerHTML=d.logs.length?d.logs.map(l=>`<div style="padding:.4rem;background:var(--bg3);border-radius:4px;margin-bottom:.25rem;font-size:.75rem"><i class="fas fa-paper-plane" style="color:var(--fire)"></i> ${l.from_email} → ${l.to_email} <span style="color:var(--text2);float:right">${new Date(l.created_at).toLocaleString()}</span></div>`).join(''):'<div class="empty">No logs</div>';}});}
function showAddAccount(){document.getElementById('addAccountModal').classList.add('active');}
function closeModal(){document.getElementById('addAccountModal').classList.remove('active');}
function addAccount(){const fd=new FormData();fd.append('action','add_account');fd.append('email',document.getElementById('accEmail').value);fd.append('password',document.getElementById('accPassword').value);fd.append('smtp_host',document.getElementById('accSmtpHost').value);fd.append('smtp_port',document.getElementById('accSmtpPort').value);fd.append('imap_host',document.getElementById('accImapHost').value);fd.append('imap_port',document.getElementById('accImapPort').value);fd.append('provider',document.getElementById('accProvider').value);fetch('',{method:'POST',body:fd}).then(r=>r.json()).then(d=>{if(d.success){alert('Added!');closeModal();loadAccounts();loadStats();}});}
function deleteAccount(id){if(!confirm('Delete?'))return;const fd=new FormData();fd.append('action','delete_account');fd.append('id',id);fetch('',{method:'POST',body:fd}).then(()=>{loadAccounts();loadStats();});}
function addMessage(){const fd=new FormData();fd.append('action','add_message');fd.append('subject',document.getElementById('msgSubject').value);fd.append('body',document.getElementById('msgBody').value);fd.append('category',document.getElementById('msgCategory').value);fetch('',{method:'POST',body:fd}).then(()=>loadMessages());}
function deleteMessage(id){if(!confirm('Delete?'))return;const fd=new FormData();fd.append('action','delete_message');fd.append('id',id);fetch('',{method:'POST',body:fd}).then(()=>loadMessages());}
function createPool(){const fd=new FormData();fd.append('action','create_pool');fd.append('name',document.getElementById('poolName').value);fd.append('description',document.getElementById('poolDesc').value);fetch('',{method:'POST',body:fd}).then(()=>{loadPools();loadStats();});}
function resetDaily(){if(!confirm('Reset daily counters?'))return;const fd=new FormData();fd.append('action','reset_daily');fetch('',{method:'POST',body:fd}).then(()=>{loadAccounts();loadStats();});}
loadStats();
loadAccounts();
</script>
</body>
</html>