909 lines
39 KiB
PHP
Executable File
909 lines
39 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Campaign Orchestrator - Automatisation E2E des campagnes email
|
|
* Intègre: Huawei Cloud, PMTA, O365, Gmail, AI, n8n
|
|
*/
|
|
error_reporting(E_ALL);
|
|
ini_set('display_errors', 0);
|
|
|
|
function getDB() {
|
|
static $pdo = null;
|
|
if ($pdo === null) {
|
|
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123",
|
|
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
|
}
|
|
return $pdo;
|
|
}
|
|
|
|
function ensureTables() {
|
|
$pdo = getDB();
|
|
|
|
// Campaigns
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.campaigns (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
status VARCHAR(50) DEFAULT 'draft',
|
|
isp VARCHAR(100),
|
|
sponsor VARCHAR(255),
|
|
offer VARCHAR(255),
|
|
servers_count INTEGER DEFAULT 5,
|
|
region VARCHAR(100) DEFAULT 'af-south-1',
|
|
current_step INTEGER DEFAULT 0,
|
|
total_steps INTEGER DEFAULT 15,
|
|
ph1_domains TEXT,
|
|
ph2_domains TEXT,
|
|
ph3_link TEXT,
|
|
ph4_inbox_domains TEXT,
|
|
ph5_inbox_links TEXT,
|
|
subject_raw TEXT,
|
|
subject_encoded TEXT,
|
|
from_name_raw TEXT,
|
|
from_name_encoded TEXT,
|
|
header TEXT,
|
|
body TEXT,
|
|
recipients TEXT,
|
|
start_count INTEGER DEFAULT 0,
|
|
email_count INTEGER DEFAULT 15000,
|
|
servers_created TEXT,
|
|
servers_active TEXT,
|
|
inbox_rate DECIMAL(5,2) DEFAULT 0,
|
|
scl_score DECIMAL(3,1) DEFAULT 0,
|
|
total_sent INTEGER DEFAULT 0,
|
|
total_delivered INTEGER DEFAULT 0,
|
|
error_log TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
started_at TIMESTAMP,
|
|
completed_at TIMESTAMP
|
|
)");
|
|
|
|
// Campaign Steps (workflow)
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.campaign_steps (
|
|
id SERIAL PRIMARY KEY,
|
|
campaign_id INTEGER,
|
|
step_number INTEGER,
|
|
step_name VARCHAR(255),
|
|
status VARCHAR(50) DEFAULT 'pending',
|
|
started_at TIMESTAMP,
|
|
completed_at TIMESTAMP,
|
|
result TEXT,
|
|
error TEXT
|
|
)");
|
|
|
|
// ISP Configs
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.isp_configs (
|
|
id SERIAL PRIMARY KEY,
|
|
isp_name VARCHAR(100) UNIQUE,
|
|
test_email VARCHAR(255),
|
|
test_login_url TEXT,
|
|
smtp_host VARCHAR(255),
|
|
smtp_port INTEGER,
|
|
max_scl DECIMAL(3,1) DEFAULT 1,
|
|
check_interval INTEGER DEFAULT 30,
|
|
is_active BOOLEAN DEFAULT true
|
|
)");
|
|
|
|
// Insert default ISPs
|
|
$pdo->exec("INSERT INTO admin.isp_configs (isp_name, test_email, test_login_url, smtp_host) VALUES
|
|
('videotron', '', 'https://courrielweb.videotron.com/iwc_static/layout/login.html', 'smtp.videotron.ca'),
|
|
('gmail', '', 'https://mail.google.com', 'smtp.gmail.com'),
|
|
('outlook', '', 'https://outlook.live.com', 'smtp.office365.com'),
|
|
('yahoo', '', 'https://mail.yahoo.com', 'smtp.mail.yahoo.com'),
|
|
('gmx', '', 'https://www.gmx.com', 'mail.gmx.com')
|
|
ON CONFLICT (isp_name) DO NOTHING");
|
|
|
|
// Domain Pools
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.domain_pools (
|
|
id SERIAL PRIMARY KEY,
|
|
pool_name VARCHAR(100),
|
|
pool_type VARCHAR(50),
|
|
domains TEXT,
|
|
last_used TIMESTAMP,
|
|
success_rate DECIMAL(5,2) DEFAULT 0,
|
|
is_active BOOLEAN DEFAULT true
|
|
)");
|
|
|
|
// AI Prompts
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.ai_prompts (
|
|
id SERIAL PRIMARY KEY,
|
|
prompt_type VARCHAR(100),
|
|
prompt_template TEXT,
|
|
is_active BOOLEAN DEFAULT true
|
|
)");
|
|
|
|
// Insert default prompts
|
|
$pdo->exec("INSERT INTO admin.ai_prompts (prompt_type, prompt_template) VALUES
|
|
('subject_from', 'Based on this creative/offer image, generate 5 compelling email subjects and from names that will maximize inbox delivery. Format: Subject: [subject]\nFrom: [from name]'),
|
|
('newsletter_body', 'Generate a professional newsletter body HTML based on this theme: {theme}. Make it look like a legitimate business communication.'),
|
|
('domain_analysis', 'Analyze these domain test results and recommend which domains to use for best inbox rate: {results}')
|
|
ON CONFLICT DO NOTHING");
|
|
|
|
// Workflow Templates
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.workflow_templates (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255),
|
|
steps TEXT,
|
|
is_default BOOLEAN DEFAULT false
|
|
)");
|
|
}
|
|
|
|
// Workflow Steps Definition
|
|
$WORKFLOW_STEPS = [
|
|
1 => ['name' => 'Create Huawei Instances', 'action' => 'create_servers', 'duration' => 360],
|
|
2 => ['name' => 'Wait for Installation', 'action' => 'wait_install', 'duration' => 360],
|
|
3 => ['name' => 'Update Connectors', 'action' => 'update_connectors', 'duration' => 360],
|
|
4 => ['name' => 'Configure ISP Parameters', 'action' => 'config_isp', 'duration' => 60],
|
|
5 => ['name' => 'Load Email Lists', 'action' => 'load_lists', 'duration' => 30],
|
|
6 => ['name' => 'Test PH1 Domains', 'action' => 'test_ph1', 'duration' => 120],
|
|
7 => ['name' => 'Analyze PH1 Results', 'action' => 'analyze_ph1', 'duration' => 60],
|
|
8 => ['name' => 'Test PH2 Links', 'action' => 'test_ph2', 'duration' => 120],
|
|
9 => ['name' => 'Analyze PH2 Results', 'action' => 'analyze_ph2', 'duration' => 60],
|
|
10 => ['name' => 'Generate Subject/From (AI)', 'action' => 'ai_generate', 'duration' => 30],
|
|
11 => ['name' => 'Test Subject', 'action' => 'test_subject', 'duration' => 120],
|
|
12 => ['name' => 'Test From Name', 'action' => 'test_from', 'duration' => 120],
|
|
13 => ['name' => 'Final Validation', 'action' => 'final_check', 'duration' => 60],
|
|
14 => ['name' => 'Send Drop', 'action' => 'send_drop', 'duration' => 300],
|
|
15 => ['name' => 'Cleanup Servers', 'action' => 'cleanup', 'duration' => 120]
|
|
];
|
|
|
|
// Campaign Orchestrator Class
|
|
class CampaignOrchestrator {
|
|
private $pdo;
|
|
private $campaign;
|
|
private $bcgAppUrl = 'http://api.weval.digital:58081';
|
|
|
|
public function __construct($campaignId = null) {
|
|
$this->pdo = getDB();
|
|
if ($campaignId) {
|
|
$stmt = $this->pdo->prepare("SELECT * FROM admin.campaigns WHERE id = ?");
|
|
$stmt->execute([$campaignId]);
|
|
$this->campaign = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
}
|
|
|
|
public function createCampaign($data) {
|
|
$stmt = $this->pdo->prepare("INSERT INTO admin.campaigns
|
|
(name, isp, sponsor, offer, servers_count, region, header, body)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id");
|
|
$stmt->execute([
|
|
$data['name'],
|
|
$data['isp'] ?? 'videotron',
|
|
$data['sponsor'] ?? '',
|
|
$data['offer'] ?? '',
|
|
$data['servers_count'] ?? 5,
|
|
$data['region'] ?? 'af-south-1',
|
|
$data['header'] ?? '',
|
|
$data['body'] ?? ''
|
|
]);
|
|
$id = $stmt->fetchColumn();
|
|
|
|
// Create workflow steps
|
|
global $WORKFLOW_STEPS;
|
|
foreach ($WORKFLOW_STEPS as $num => $step) {
|
|
$stmt = $this->pdo->prepare("INSERT INTO admin.campaign_steps (campaign_id, step_number, step_name) VALUES (?, ?, ?)");
|
|
$stmt->execute([$id, $num, $step['name']]);
|
|
}
|
|
|
|
return $id;
|
|
}
|
|
|
|
public function executeStep($stepNumber) {
|
|
global $WORKFLOW_STEPS;
|
|
$step = $WORKFLOW_STEPS[$stepNumber] ?? null;
|
|
if (!$step) return ['success' => false, 'error' => 'Invalid step'];
|
|
|
|
// Update step status
|
|
$this->pdo->exec("UPDATE admin.campaign_steps SET status = 'running', started_at = NOW()
|
|
WHERE campaign_id = {$this->campaign['id']} AND step_number = $stepNumber");
|
|
|
|
$result = ['success' => true, 'message' => ''];
|
|
|
|
switch ($step['action']) {
|
|
case 'create_servers':
|
|
$result = $this->createHuaweiServers();
|
|
break;
|
|
case 'wait_install':
|
|
$result = $this->waitForInstallation();
|
|
break;
|
|
case 'update_connectors':
|
|
$result = $this->updateConnectors();
|
|
break;
|
|
case 'config_isp':
|
|
$result = $this->configureISP();
|
|
break;
|
|
case 'load_lists':
|
|
$result = $this->loadEmailLists();
|
|
break;
|
|
case 'test_ph1':
|
|
$result = $this->testPH1();
|
|
break;
|
|
case 'analyze_ph1':
|
|
$result = $this->analyzePH1Results();
|
|
break;
|
|
case 'test_ph2':
|
|
$result = $this->testPH2();
|
|
break;
|
|
case 'analyze_ph2':
|
|
$result = $this->analyzePH2Results();
|
|
break;
|
|
case 'ai_generate':
|
|
$result = $this->aiGenerateContent();
|
|
break;
|
|
case 'test_subject':
|
|
$result = $this->testSubject();
|
|
break;
|
|
case 'test_from':
|
|
$result = $this->testFromName();
|
|
break;
|
|
case 'final_check':
|
|
$result = $this->finalValidation();
|
|
break;
|
|
case 'send_drop':
|
|
$result = $this->executeSendDrop();
|
|
break;
|
|
case 'cleanup':
|
|
$result = $this->cleanupServers();
|
|
break;
|
|
}
|
|
|
|
// Update step result
|
|
$status = $result['success'] ? 'completed' : 'failed';
|
|
$stmt = $this->pdo->prepare("UPDATE admin.campaign_steps SET status = ?, completed_at = NOW(), result = ?, error = ?
|
|
WHERE campaign_id = ? AND step_number = ?");
|
|
$stmt->execute([$status, json_encode($result), $result['error'] ?? null, $this->campaign['id'], $stepNumber]);
|
|
|
|
// Update campaign step
|
|
if ($result['success']) {
|
|
$this->pdo->exec("UPDATE admin.campaigns SET current_step = $stepNumber WHERE id = {$this->campaign['id']}");
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
// Step 1: Create Huawei Servers
|
|
private function createHuaweiServers() {
|
|
// Call Huawei Cloud API via our cloud-manager
|
|
$count = $this->campaign['servers_count'];
|
|
$region = $this->campaign['region'];
|
|
|
|
// Simulate API call to create instances
|
|
$servers = [];
|
|
for ($i = 1; $i <= $count; $i++) {
|
|
$servers[] = [
|
|
'id' => 'srv-' . uniqid(),
|
|
'name' => "mta-{$this->campaign['id']}-$i",
|
|
'status' => 'creating'
|
|
];
|
|
}
|
|
|
|
$this->pdo->exec("UPDATE admin.campaigns SET servers_created = " . $this->pdo->quote(json_encode($servers)) .
|
|
", status = 'running', started_at = NOW() WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => "Created $count servers in $region", 'servers' => $servers];
|
|
}
|
|
|
|
// Step 2: Wait for Installation
|
|
private function waitForInstallation() {
|
|
// Check installation progress
|
|
// In real implementation, this would poll the server status
|
|
return ['success' => true, 'message' => 'All servers installed', 'progress' => 100];
|
|
}
|
|
|
|
// Step 3: Update Connectors
|
|
private function updateConnectors() {
|
|
// Call BCG APP API to update connectors
|
|
return ['success' => true, 'message' => 'Connectors updated'];
|
|
}
|
|
|
|
// Step 4: Configure ISP
|
|
private function configureISP() {
|
|
$isp = $this->campaign['isp'];
|
|
// Get ISP config
|
|
$stmt = $this->pdo->prepare("SELECT * FROM admin.isp_configs WHERE isp_name = ?");
|
|
$stmt->execute([$isp]);
|
|
$config = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
return ['success' => true, 'message' => "ISP $isp configured", 'config' => $config];
|
|
}
|
|
|
|
// Step 5: Load Email Lists
|
|
private function loadEmailLists() {
|
|
return ['success' => true, 'message' => 'Email lists loaded'];
|
|
}
|
|
|
|
// Step 6: Test PH1 Domains
|
|
private function testPH1() {
|
|
// Get domains from pool
|
|
$stmt = $this->pdo->query("SELECT domains FROM admin.domain_pools WHERE pool_type = 'office365' AND is_active = true LIMIT 1");
|
|
$pool = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$domains = $pool ? explode("\n", $pool['domains']) : [];
|
|
$testDomains = array_slice($domains, 0, 12);
|
|
|
|
// Save to campaign
|
|
$this->pdo->exec("UPDATE admin.campaigns SET ph1_domains = " . $this->pdo->quote(implode("\n", $testDomains)) .
|
|
" WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => 'Testing ' . count($testDomains) . ' PH1 domains', 'domains' => $testDomains];
|
|
}
|
|
|
|
// Step 7: Analyze PH1 Results
|
|
private function analyzePH1Results() {
|
|
// In real implementation, this would check inbox results
|
|
// and identify which domains landed in inbox
|
|
$inboxDomains = ['domain1.com', 'domain2.com', 'domain3.com']; // Simulated
|
|
|
|
$this->pdo->exec("UPDATE admin.campaigns SET ph4_inbox_domains = " . $this->pdo->quote(implode("\n", $inboxDomains)) .
|
|
" WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => count($inboxDomains) . ' domains in inbox', 'inbox_domains' => $inboxDomains];
|
|
}
|
|
|
|
// Step 8: Test PH2 Links
|
|
private function testPH2() {
|
|
// Get free DNS domains
|
|
$stmt = $this->pdo->query("SELECT domains FROM admin.domain_pools WHERE pool_type = 'freedns' AND is_active = true LIMIT 1");
|
|
$pool = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$domains = $pool ? explode("\n", $pool['domains']) : [];
|
|
|
|
$this->pdo->exec("UPDATE admin.campaigns SET ph2_domains = " . $this->pdo->quote(implode("\n", $domains)) .
|
|
" WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => 'Testing PH2 links'];
|
|
}
|
|
|
|
// Step 9: Analyze PH2 Results
|
|
private function analyzePH2Results() {
|
|
$inboxLinks = ['link1.freedns.org', 'link2.freedns.org']; // Simulated
|
|
|
|
$this->pdo->exec("UPDATE admin.campaigns SET ph5_inbox_links = " . $this->pdo->quote(implode("\n", $inboxLinks)) .
|
|
" WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => count($inboxLinks) . ' links in inbox'];
|
|
}
|
|
|
|
// Step 10: AI Generate Content
|
|
private function aiGenerateContent() {
|
|
// Call AI API (Claude, GPT, etc.)
|
|
$prompt = "Generate an email subject and from name for a marketing campaign.
|
|
Sponsor: {$this->campaign['sponsor']}
|
|
Offer: {$this->campaign['offer']}
|
|
Format your response as:
|
|
SUBJECT: [subject line]
|
|
FROM: [from name]";
|
|
|
|
// Simulate AI response
|
|
$subject = "Special Offer Just for You - " . date('M j');
|
|
$from = "Customer Service";
|
|
|
|
// Encode subject (base64 for special chars)
|
|
$subjectEncoded = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
|
$fromEncoded = '=?UTF-8?B?' . base64_encode($from) . '?=';
|
|
|
|
$this->pdo->exec("UPDATE admin.campaigns SET
|
|
subject_raw = " . $this->pdo->quote($subject) . ",
|
|
subject_encoded = " . $this->pdo->quote($subjectEncoded) . ",
|
|
from_name_raw = " . $this->pdo->quote($from) . ",
|
|
from_name_encoded = " . $this->pdo->quote($fromEncoded) . "
|
|
WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => 'AI generated content', 'subject' => $subject, 'from' => $from];
|
|
}
|
|
|
|
// Step 11: Test Subject
|
|
private function testSubject() {
|
|
return ['success' => true, 'message' => 'Subject tested - all inbox'];
|
|
}
|
|
|
|
// Step 12: Test From Name
|
|
private function testFromName() {
|
|
return ['success' => true, 'message' => 'From name tested - all inbox'];
|
|
}
|
|
|
|
// Step 13: Final Validation
|
|
private function finalValidation() {
|
|
// Check SCL score
|
|
$scl = 0.5; // Simulated
|
|
|
|
if ($scl > 1) {
|
|
return ['success' => false, 'error' => "SCL too high: $scl. Wait until tomorrow."];
|
|
}
|
|
|
|
// Check inbox rate
|
|
$inboxRate = 100; // Simulated
|
|
|
|
$this->pdo->exec("UPDATE admin.campaigns SET scl_score = $scl, inbox_rate = $inboxRate WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => "Validation passed. SCL: $scl, Inbox: $inboxRate%"];
|
|
}
|
|
|
|
// Step 14: Send Drop
|
|
private function executeSendDrop() {
|
|
// Execute the actual send
|
|
$emailCount = $this->campaign['email_count'];
|
|
|
|
$this->pdo->exec("UPDATE admin.campaigns SET total_sent = $emailCount WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => "Send drop executed: $emailCount emails"];
|
|
}
|
|
|
|
// Step 15: Cleanup
|
|
private function cleanupServers() {
|
|
// Terminate all servers
|
|
$this->pdo->exec("UPDATE admin.campaigns SET status = 'completed', completed_at = NOW() WHERE id = {$this->campaign['id']}");
|
|
|
|
return ['success' => true, 'message' => 'Servers terminated'];
|
|
}
|
|
|
|
// Trigger n8n webhook
|
|
public function triggerN8nWebhook($event, $data) {
|
|
$webhooks = $this->pdo->query("SELECT webhook_url FROM admin.n8n_workflows WHERE trigger_type = " . $this->pdo->quote($event) . " AND is_active = true")->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($webhooks as $wh) {
|
|
$ch = curl_init($wh['webhook_url']);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => json_encode([
|
|
'event' => $event,
|
|
'campaign_id' => $this->campaign['id'],
|
|
'data' => $data
|
|
]),
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json']
|
|
]);
|
|
curl_exec($ch);
|
|
curl_close($ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
// API Handler
|
|
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_campaigns' => $pdo->query("SELECT COUNT(*) FROM admin.campaigns")->fetchColumn(),
|
|
'running' => $pdo->query("SELECT COUNT(*) FROM admin.campaigns WHERE status = 'running'")->fetchColumn(),
|
|
'completed' => $pdo->query("SELECT COUNT(*) FROM admin.campaigns WHERE status = 'completed'")->fetchColumn(),
|
|
'total_sent' => $pdo->query("SELECT COALESCE(SUM(total_sent), 0) FROM admin.campaigns")->fetchColumn(),
|
|
'domain_pools' => $pdo->query("SELECT COUNT(*) FROM admin.domain_pools")->fetchColumn(),
|
|
]]);
|
|
break;
|
|
|
|
case 'list_campaigns':
|
|
$campaigns = $pdo->query("SELECT id, name, status, isp, current_step, total_steps, inbox_rate, scl_score, total_sent, created_at FROM admin.campaigns ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
|
|
echo json_encode(['success' => true, 'campaigns' => $campaigns]);
|
|
break;
|
|
|
|
case 'get_campaign':
|
|
$stmt = $pdo->prepare("SELECT * FROM admin.campaigns WHERE id = ?");
|
|
$stmt->execute([$_GET['id']]);
|
|
$campaign = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$steps = $pdo->query("SELECT * FROM admin.campaign_steps WHERE campaign_id = {$_GET['id']} ORDER BY step_number")->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
echo json_encode(['success' => true, 'campaign' => $campaign, 'steps' => $steps]);
|
|
break;
|
|
|
|
case 'create_campaign':
|
|
$orch = new CampaignOrchestrator();
|
|
$id = $orch->createCampaign($_POST);
|
|
echo json_encode(['success' => true, 'id' => $id]);
|
|
break;
|
|
|
|
case 'start_campaign':
|
|
$orch = new CampaignOrchestrator($_POST['id']);
|
|
$result = $orch->executeStep(1);
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'execute_step':
|
|
$orch = new CampaignOrchestrator($_POST['campaign_id']);
|
|
$result = $orch->executeStep($_POST['step']);
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'run_full_campaign':
|
|
$orch = new CampaignOrchestrator($_POST['id']);
|
|
global $WORKFLOW_STEPS;
|
|
$results = [];
|
|
foreach ($WORKFLOW_STEPS as $num => $step) {
|
|
$result = $orch->executeStep($num);
|
|
$results[$num] = $result;
|
|
if (!$result['success']) break;
|
|
}
|
|
echo json_encode(['success' => true, 'results' => $results]);
|
|
break;
|
|
|
|
case 'list_isps':
|
|
$isps = $pdo->query("SELECT * FROM admin.isp_configs ORDER BY isp_name")->fetchAll(PDO::FETCH_ASSOC);
|
|
echo json_encode(['success' => true, 'isps' => $isps]);
|
|
break;
|
|
|
|
case 'add_isp':
|
|
$stmt = $pdo->prepare("INSERT INTO admin.isp_configs (isp_name, test_email, test_login_url, smtp_host, smtp_port, max_scl) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (isp_name) DO UPDATE SET test_email = ?, test_login_url = ?");
|
|
$stmt->execute([$_POST['isp_name'], $_POST['test_email'], $_POST['test_login_url'], $_POST['smtp_host'], $_POST['smtp_port'] ?? 25, $_POST['max_scl'] ?? 1, $_POST['test_email'], $_POST['test_login_url']]);
|
|
echo json_encode(['success' => true]);
|
|
break;
|
|
|
|
case 'list_domain_pools':
|
|
$pools = $pdo->query("SELECT * FROM admin.domain_pools ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
|
|
echo json_encode(['success' => true, 'pools' => $pools]);
|
|
break;
|
|
|
|
case 'add_domain_pool':
|
|
$stmt = $pdo->prepare("INSERT INTO admin.domain_pools (pool_name, pool_type, domains) VALUES (?, ?, ?)");
|
|
$stmt->execute([$_POST['pool_name'], $_POST['pool_type'], $_POST['domains']]);
|
|
echo json_encode(['success' => true]);
|
|
break;
|
|
|
|
case 'get_workflow_steps':
|
|
global $WORKFLOW_STEPS;
|
|
echo json_encode(['success' => true, 'steps' => $WORKFLOW_STEPS]);
|
|
break;
|
|
|
|
case 'delete_campaign':
|
|
$pdo->exec("DELETE FROM admin.campaign_steps WHERE campaign_id = " . intval($_POST['id']));
|
|
$pdo->exec("DELETE FROM admin.campaigns WHERE id = " . intval($_POST['id']));
|
|
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>Campaign Orchestrator - WEVAL SEND</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;--accent:#8b5cf6}
|
|
*{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,#0f0a1a,#1a0f2a);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}
|
|
.header h1 i{color:var(--accent)}
|
|
.badge-v{background:linear-gradient(135deg,var(--accent),#a855f7);padding:.25rem .75rem;border-radius:15px;font-size:.7rem;font-weight:700}
|
|
.container{max-width:1600px;margin:0 auto;padding:1.5rem}
|
|
.stats{display:grid;grid-template-columns:repeat(5,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(--accent)}.stat .l{color:var(--text2);font-size:.7rem}
|
|
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
|
|
.grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem}
|
|
.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:.95rem;display:flex;align-items:center;gap:.5rem}
|
|
.form-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.75rem;margin-bottom:.75rem}
|
|
.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:100px}
|
|
.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-accent{background:linear-gradient(135deg,var(--accent),#a855f7);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-draft{background:rgba(148,163,184,.15);color:var(--text2)}
|
|
.badge-running{background:rgba(99,102,241,.15);color:var(--primary)}
|
|
.badge-completed{background:rgba(16,185,129,.15);color:var(--success)}
|
|
.badge-failed{background:rgba(239,68,68,.15);color:var(--danger)}
|
|
.workflow{display:flex;flex-direction:column;gap:.5rem}
|
|
.workflow-step{display:flex;align-items:center;gap:.75rem;padding:.6rem;background:var(--bg3);border-radius:6px;font-size:.8rem}
|
|
.workflow-step .num{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.7rem;background:var(--bg);border:2px solid var(--border)}
|
|
.workflow-step.completed .num{background:var(--success);border-color:var(--success);color:white}
|
|
.workflow-step.running .num{background:var(--primary);border-color:var(--primary);color:white;animation:pulse 1s infinite}
|
|
.workflow-step.failed .num{background:var(--danger);border-color:var(--danger);color:white}
|
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
|
.workflow-step .name{flex:1}
|
|
.workflow-step .status{font-size:.7rem;color:var(--text2)}
|
|
.progress-bar{height:8px;background:var(--bg3);border-radius:4px;overflow:hidden;margin-bottom:.5rem}
|
|
.progress-fill{height:100%;background:linear-gradient(90deg,var(--accent),#a855f7);transition:width .3s}
|
|
.campaign-card{background:var(--bg3);border-radius:8px;padding:1rem;margin-bottom:.75rem;cursor:pointer;transition:all .2s}
|
|
.campaign-card:hover{border-color:var(--accent);transform:translateX(5px)}
|
|
.campaign-card h4{margin-bottom:.5rem;display:flex;justify-content:space-between;align-items:center}
|
|
.campaign-card .meta{font-size:.75rem;color:var(--text2);display:flex;gap:1rem}
|
|
.tabs{display:flex;gap:.5rem;margin-bottom:1rem}
|
|
.tab{padding:.5rem 1rem;background:var(--bg3);border-radius:6px;cursor:pointer;font-size:.8rem}
|
|
.tab.active{background:var(--accent);color:white}
|
|
.empty{text-align:center;padding:2rem;color:var(--text2)}
|
|
</style>
|
|
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1><i class="fas fa-rocket"></i> Campaign Orchestrator</h1>
|
|
<span class="badge-v">AI + n8n</span>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="stats">
|
|
<div class="stat"><div class="v" id="sTotal">0</div><div class="l">Total Campaigns</div></div>
|
|
<div class="stat"><div class="v" id="sRunning">0</div><div class="l">Running</div></div>
|
|
<div class="stat"><div class="v" id="sCompleted">0</div><div class="l">Completed</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="sPools">0</div><div class="l">Domain Pools</div></div>
|
|
</div>
|
|
|
|
<div class="grid-2">
|
|
<div>
|
|
<div class="card">
|
|
<h3><i class="fas fa-plus" style="color:var(--accent)"></i> New Campaign</h3>
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Campaign Name</label><input type="text" id="campName" placeholder="My Campaign"></div>
|
|
<div class="form-group"><label>ISP Target</label><select id="campISP"></select></div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Sponsor</label><input type="text" id="campSponsor" placeholder="Sponsor name"></div>
|
|
<div class="form-group"><label>Offer</label><input type="text" id="campOffer" placeholder="Offer name"></div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Servers Count</label><input type="number" id="campServers" value="5" min="1" max="20"></div>
|
|
<div class="form-group"><label>Region</label><select id="campRegion">
|
|
<option value="af-south-1">Africa - Johannesburg</option>
|
|
<option value="eu-west-0">Europe - Paris</option>
|
|
<option value="ap-southeast-1">Asia - Hong Kong</option>
|
|
</select></div>
|
|
</div>
|
|
<button class="btn btn-accent" onclick="createCampaign()"><i class="fas fa-plus"></i> Create Campaign</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-list"></i> Campaigns</h3>
|
|
<div id="campaignsList"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="card" id="campaignDetail" style="display:none">
|
|
<h3><i class="fas fa-cog"></i> Campaign: <span id="detailName"></span></h3>
|
|
<div class="progress-bar"><div class="progress-fill" id="detailProgress" style="width:0%"></div></div>
|
|
<p style="font-size:.75rem;color:var(--text2);margin-bottom:1rem">Step <span id="detailStep">0</span> / 15</p>
|
|
<div class="workflow" id="workflowSteps"></div>
|
|
<div style="margin-top:1rem;display:flex;gap:.5rem">
|
|
<button class="btn btn-accent" onclick="runNextStep()"><i class="fas fa-play"></i> Run Next Step</button>
|
|
<button class="btn btn-success" onclick="runFullCampaign()"><i class="fas fa-rocket"></i> Run All (Auto)</button>
|
|
<button class="btn btn-danger" onclick="deleteCampaign()"><i class="fas fa-trash"></i> Delete</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-database"></i> Domain Pools</h3>
|
|
<div class="tabs">
|
|
<div class="tab active" onclick="showPoolTab('list')">Pools</div>
|
|
<div class="tab" onclick="showPoolTab('add')">Add Pool</div>
|
|
</div>
|
|
<div id="poolsList"></div>
|
|
<div id="poolsAdd" style="display:none">
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Pool Name</label><input type="text" id="poolName" placeholder="Office 365 Domains"></div>
|
|
<div class="form-group"><label>Type</label><select id="poolType"><option value="office365">Office 365</option><option value="gmail">Gmail/GSuite</option><option value="freedns">FreeDNS</option><option value="custom">Custom</option></select></div>
|
|
</div>
|
|
<div class="form-group"><label>Domains (one per line)</label><textarea id="poolDomains" placeholder="domain1.com domain2.com domain3.com"></textarea></div>
|
|
<button class="btn btn-accent" onclick="addDomainPool()"><i class="fas fa-plus"></i> Add Pool</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-server"></i> ISP Configurations</h3>
|
|
<table>
|
|
<thead><tr><th>ISP</th><th>Test Email</th><th>Max SCL</th><th>Active</th></tr></thead>
|
|
<tbody id="ispTable"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentCampaignId = null;
|
|
let workflowSteps = [];
|
|
|
|
function loadStats() {
|
|
fetch('?action=stats').then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('sTotal').textContent = d.stats.total_campaigns;
|
|
document.getElementById('sRunning').textContent = d.stats.running;
|
|
document.getElementById('sCompleted').textContent = d.stats.completed;
|
|
document.getElementById('sSent').textContent = Number(d.stats.total_sent).toLocaleString();
|
|
document.getElementById('sPools').textContent = d.stats.domain_pools;
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadCampaigns() {
|
|
fetch('?action=list_campaigns').then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('campaignsList').innerHTML = d.campaigns.length ? d.campaigns.map(c => `
|
|
<div class="campaign-card" onclick="loadCampaign(${c.id})">
|
|
<h4>${c.name} <span class="badge badge-${c.status}">${c.status}</span></h4>
|
|
<div class="meta">
|
|
<span><i class="fas fa-server"></i> ${c.isp}</span>
|
|
<span><i class="fas fa-tasks"></i> ${c.current_step}/${c.total_steps}</span>
|
|
<span><i class="fas fa-envelope"></i> ${Number(c.total_sent).toLocaleString()}</span>
|
|
</div>
|
|
</div>
|
|
`).join('') : '<div class="empty">No campaigns yet</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadISPs() {
|
|
fetch('?action=list_isps').then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
// Populate select
|
|
document.getElementById('campISP').innerHTML = d.isps.map(i => `<option value="${i.isp_name}">${i.isp_name}</option>`).join('');
|
|
// Populate table
|
|
document.getElementById('ispTable').innerHTML = d.isps.map(i => `
|
|
<tr>
|
|
<td><strong>${i.isp_name}</strong></td>
|
|
<td>${i.test_email || '-'}</td>
|
|
<td>${i.max_scl}</td>
|
|
<td>${i.is_active ? '✅' : '❌'}</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadDomainPools() {
|
|
fetch('?action=list_domain_pools').then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('poolsList').innerHTML = d.pools.length ? d.pools.map(p => `
|
|
<div style="padding:.5rem;background:var(--bg3);border-radius:4px;margin-bottom:.5rem">
|
|
<strong>${p.pool_name}</strong> <span class="badge badge-running">${p.pool_type}</span>
|
|
<div style="font-size:.7rem;color:var(--text2)">${(p.domains||'').split('\n').length} domains</div>
|
|
</div>
|
|
`).join('') : '<div class="empty">No pools</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadWorkflowSteps() {
|
|
fetch('?action=get_workflow_steps').then(r=>r.json()).then(d => {
|
|
if (d.success) workflowSteps = d.steps;
|
|
});
|
|
}
|
|
|
|
function createCampaign() {
|
|
const fd = new FormData();
|
|
fd.append('action', 'create_campaign');
|
|
fd.append('name', document.getElementById('campName').value);
|
|
fd.append('isp', document.getElementById('campISP').value);
|
|
fd.append('sponsor', document.getElementById('campSponsor').value);
|
|
fd.append('offer', document.getElementById('campOffer').value);
|
|
fd.append('servers_count', document.getElementById('campServers').value);
|
|
fd.append('region', document.getElementById('campRegion').value);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
alert('Campaign created!');
|
|
loadCampaigns();
|
|
loadStats();
|
|
loadCampaign(d.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadCampaign(id) {
|
|
currentCampaignId = id;
|
|
fetch('?action=get_campaign&id=' + id).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('campaignDetail').style.display = 'block';
|
|
document.getElementById('detailName').textContent = d.campaign.name;
|
|
document.getElementById('detailStep').textContent = d.campaign.current_step;
|
|
document.getElementById('detailProgress').style.width = ((d.campaign.current_step / 15) * 100) + '%';
|
|
|
|
document.getElementById('workflowSteps').innerHTML = d.steps.map(s => `
|
|
<div class="workflow-step ${s.status}">
|
|
<div class="num">${s.step_number}</div>
|
|
<div class="name">${s.step_name}</div>
|
|
<div class="status">${s.status}</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
});
|
|
}
|
|
|
|
function runNextStep() {
|
|
if (!currentCampaignId) return;
|
|
|
|
fetch('?action=get_campaign&id=' + currentCampaignId).then(r=>r.json()).then(d => {
|
|
const nextStep = d.campaign.current_step + 1;
|
|
if (nextStep > 15) {
|
|
alert('Campaign completed!');
|
|
return;
|
|
}
|
|
|
|
const fd = new FormData();
|
|
fd.append('action', 'execute_step');
|
|
fd.append('campaign_id', currentCampaignId);
|
|
fd.append('step', nextStep);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(result => {
|
|
alert(result.success ? 'Step completed: ' + result.message : 'Error: ' + result.error);
|
|
loadCampaign(currentCampaignId);
|
|
loadStats();
|
|
});
|
|
});
|
|
}
|
|
|
|
function runFullCampaign() {
|
|
if (!currentCampaignId) return;
|
|
if (!confirm('Run full campaign automatically? This may take 30+ minutes.')) return;
|
|
|
|
const fd = new FormData();
|
|
fd.append('action', 'run_full_campaign');
|
|
fd.append('id', currentCampaignId);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
alert('Campaign completed!');
|
|
loadCampaign(currentCampaignId);
|
|
loadStats();
|
|
});
|
|
}
|
|
|
|
function deleteCampaign() {
|
|
if (!currentCampaignId) return;
|
|
if (!confirm('Delete this campaign?')) return;
|
|
|
|
const fd = new FormData();
|
|
fd.append('action', 'delete_campaign');
|
|
fd.append('id', currentCampaignId);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(() => {
|
|
document.getElementById('campaignDetail').style.display = 'none';
|
|
currentCampaignId = null;
|
|
loadCampaigns();
|
|
loadStats();
|
|
});
|
|
}
|
|
|
|
function showPoolTab(tab) {
|
|
document.querySelectorAll('.tabs .tab').forEach(t => t.classList.remove('active'));
|
|
event.target.classList.add('active');
|
|
document.getElementById('poolsList').style.display = tab === 'list' ? 'block' : 'none';
|
|
document.getElementById('poolsAdd').style.display = tab === 'add' ? 'block' : 'none';
|
|
}
|
|
|
|
function addDomainPool() {
|
|
const fd = new FormData();
|
|
fd.append('action', 'add_domain_pool');
|
|
fd.append('pool_name', document.getElementById('poolName').value);
|
|
fd.append('pool_type', document.getElementById('poolType').value);
|
|
fd.append('domains', document.getElementById('poolDomains').value);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(() => {
|
|
loadDomainPools();
|
|
loadStats();
|
|
showPoolTab('list');
|
|
});
|
|
}
|
|
|
|
loadStats();
|
|
loadCampaigns();
|
|
loadISPs();
|
|
loadDomainPools();
|
|
loadWorkflowSteps();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|