Files
wevads-platform/scripts/webhook-manager.php
2026-02-26 04:53:11 +01:00

175 lines
14 KiB
PHP
Executable File

<?php
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->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return $pdo;
}
function ensureTables($pdo) {
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.webhooks (id SERIAL PRIMARY KEY, name VARCHAR(255), url TEXT, method VARCHAR(10) DEFAULT 'POST', headers JSONB, status VARCHAR(50) DEFAULT 'active', last_triggered TIMESTAMP, success_count INTEGER DEFAULT 0, error_count INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT NOW())");
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.webhook_logs (id SERIAL PRIMARY KEY, webhook_id INTEGER, request_body TEXT, response_code INTEGER, response_body TEXT, latency_ms INTEGER, status VARCHAR(50), created_at TIMESTAMP DEFAULT NOW())");
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.webhook_incoming (id SERIAL PRIMARY KEY, endpoint VARCHAR(100), method VARCHAR(10), headers JSONB, body TEXT, ip VARCHAR(50), created_at TIMESTAMP DEFAULT NOW())");
}
function triggerWebhook($webhook, $payload) {
$start = microtime(true);
$ch = curl_init($webhook['url']);
$headers = json_decode($webhook['headers'] ?: '{}', true) ?: [];
$headerList = ['Content-Type: application/json'];
foreach ($headers as $k => $v) $headerList[] = "$k: $v";
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $webhook['method'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headerList, CURLOPT_TIMEOUT => 30]);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['code' => $code, 'response' => $response, 'latency' => round((microtime(true) - $start) * 1000), 'success' => $code >= 200 && $code < 300];
}
if (isset($_GET['incoming'])) {
$pdo = getDb();
ensureTables($pdo);
$stmt = $pdo->prepare("INSERT INTO admin.webhook_incoming (endpoint, method, headers, body, ip) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$_GET['incoming'], $_SERVER['REQUEST_METHOD'], json_encode(getallheaders()), file_get_contents('php://input'), $_SERVER['REMOTE_ADDR']]);
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
exit;
}
if (isset($_GET['action'])) {
header('Content-Type: application/json');
$pdo = getDb();
ensureTables($pdo);
switch ($_GET['action']) {
case 'stats':
$webhooks = $pdo->query("SELECT COUNT(*) FROM admin.webhooks")->fetchColumn();
$today = $pdo->query("SELECT COUNT(*) FROM admin.webhook_logs WHERE created_at > NOW() - INTERVAL '24 hours'")->fetchColumn();
$success = $pdo->query("SELECT COUNT(*) FROM admin.webhook_logs WHERE status = 'success' AND created_at > NOW() - INTERVAL '24 hours'")->fetchColumn();
$incoming = $pdo->query("SELECT COUNT(*) FROM admin.webhook_incoming WHERE created_at > NOW() - INTERVAL '24 hours'")->fetchColumn();
echo json_encode(['webhooks' => (int)$webhooks, 'today' => (int)$today, 'success' => (int)$success, 'incoming' => (int)$incoming]);
break;
case 'list':
echo json_encode($pdo->query("SELECT * FROM admin.webhooks ORDER BY name")->fetchAll(PDO::FETCH_ASSOC));
break;
case 'add':
$stmt = $pdo->prepare("INSERT INTO admin.webhooks (name, url, method, headers) VALUES (?, ?, ?, ?)");
$stmt->execute([$_POST['name'] ?? '', $_POST['url'] ?? '', $_POST['method'] ?? 'POST', $_POST['headers'] ?? '{}']);
echo json_encode(['success' => true]);
break;
case 'delete':
$pdo->exec("DELETE FROM admin.webhooks WHERE id = " . intval($_POST['id'] ?? 0));
echo json_encode(['success' => true]);
break;
case 'trigger':
$id = intval($_POST['id'] ?? 0);
$payload = json_decode($_POST['payload'] ?? '{}', true) ?: [];
$webhook = $pdo->query("SELECT * FROM admin.webhooks WHERE id = $id")->fetch(PDO::FETCH_ASSOC);
if (!$webhook) { echo json_encode(['error' => 'Not found']); break; }
$result = triggerWebhook($webhook, $payload);
$stmt = $pdo->prepare("INSERT INTO admin.webhook_logs (webhook_id, request_body, response_code, response_body, latency_ms, status) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$id, json_encode($payload), $result['code'], $result['response'], $result['latency'], $result['success'] ? 'success' : 'error']);
$field = $result['success'] ? 'success_count' : 'error_count';
$pdo->exec("UPDATE admin.webhooks SET $field = $field + 1, last_triggered = NOW() WHERE id = $id");
echo json_encode($result);
break;
case 'logs':
$where = isset($_GET['webhook_id']) ? "WHERE webhook_id = " . intval($_GET['webhook_id']) : "";
echo json_encode($pdo->query("SELECT l.*, w.name as webhook_name FROM admin.webhook_logs l LEFT JOIN admin.webhooks w ON l.webhook_id = w.id $where ORDER BY l.created_at DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC));
break;
case 'incoming_logs':
echo json_encode($pdo->query("SELECT * FROM admin.webhook_incoming ORDER BY created_at DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC));
break;
default:
echo json_encode(['error' => 'Unknown']);
}
exit;
}
$pdo = getDb();
ensureTables($pdo);
$incomingUrl = 'http://' . $_SERVER['HTTP_HOST'] . '/webhook-manager.php?incoming=my-endpoint';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webhook Manager - 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;--card:#12121a;--card2:#1a1a25;--border:#2a2a3a;--text:#e4e4e7;--text2:#9ca3af;--primary:#6366f1;--success:#10b981;--warning:#f59e0b;--danger:#ef4444}
*{margin:0;padding:0;box-sizing:border-box}body{font-family:Inter,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;padding:20px}.container{max-width:1200px;margin:0 auto}h1{font-size:24px;margin-bottom:24px;display:flex;align-items:center;gap:12px}h1 i{color:var(--primary)}
.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:24px}.stat-card{background:var(--card);border-radius:12px;padding:20px;border:1px solid var(--border)}.stat-value{font-size:28px;font-weight:700}.stat-label{color:var(--text2);font-size:12px;margin-top:4px}
.tabs{display:flex;gap:10px;margin-bottom:20px}.tab{padding:10px 20px;background:var(--card);border:1px solid var(--border);border-radius:8px;cursor:pointer;font-size:13px}.tab.active{background:var(--primary);border-color:var(--primary)}
.panel{background:var(--card);border-radius:12px;border:1px solid var(--border);margin-bottom:20px;display:none}.panel.active{display:block}.panel-header{padding:16px 20px;border-bottom:1px solid var(--border)}.panel-body{padding:20px}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}.form-group{margin-bottom:16px}.form-group label{display:block;margin-bottom:6px;font-size:12px;color:var(--text2)}.form-group input,.form-group select{width:100%;padding:10px;background:var(--card2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px}
.btn{padding:10px 20px;border:none;border-radius:8px;cursor:pointer;font-weight:600;font-size:12px;display:inline-flex;align-items:center;gap:8px}.btn-primary{background:var(--primary);color:white}.btn-success{background:var(--success);color:white}.btn-danger{background:var(--danger);color:white}.btn-sm{padding:6px 12px;font-size:11px}.btn:hover{filter:brightness(1.1)}
.table{width:100%;border-collapse:collapse}.table th,.table td{padding:10px;text-align:left;border-bottom:1px solid var(--border);font-size:12px}.table th{background:var(--card2);color:var(--text2)}
.badge{padding:3px 8px;border-radius:12px;font-size:10px;font-weight:600}.badge-success{background:rgba(16,185,129,0.2);color:var(--success)}.badge-danger{background:rgba(239,68,68,0.2);color:var(--danger)}
.code-box{background:var(--card2);padding:12px;border-radius:8px;font-family:monospace;font-size:12px}
.toast{position:fixed;bottom:20px;right:20px;background:var(--card);border:1px solid var(--border);padding:12px 20px;border-radius:8px;display:none;font-size:13px}.toast.show{display:block}
</style>
</head>
<body>
<div class="container">
<h1><i class="fas fa-plug"></i> Webhook Manager</h1>
<div class="stats-grid">
<div class="stat-card"><div class="stat-value" id="statWebhooks">-</div><div class="stat-label">Webhooks</div></div>
<div class="stat-card"><div class="stat-value" id="statToday">-</div><div class="stat-label">Triggered 24h</div></div>
<div class="stat-card"><div class="stat-value" style="color:var(--success)" id="statSuccess">-</div><div class="stat-label">Success</div></div>
<div class="stat-card"><div class="stat-value" id="statIncoming">-</div><div class="stat-label">Incoming 24h</div></div>
</div>
<div class="tabs">
<div class="tab active" onclick="showTab('outgoing')"><i class="fas fa-arrow-right"></i> Outgoing</div>
<div class="tab" onclick="showTab('incoming')"><i class="fas fa-arrow-left"></i> Incoming</div>
<div class="tab" onclick="showTab('logs')"><i class="fas fa-history"></i> Logs</div>
</div>
<div class="panel active" id="panel-outgoing">
<div class="panel-header"><h2>Outgoing Webhooks</h2></div>
<div class="panel-body">
<div class="form-row">
<div class="form-group"><label>Name</label><input type="text" id="whName" placeholder="My Webhook"></div>
<div class="form-group"><label>URL</label><input type="url" id="whUrl" placeholder="https://example.com/webhook"></div>
</div>
<div class="form-row">
<div class="form-group"><label>Method</label><select id="whMethod"><option>POST</option><option>GET</option><option>PUT</option></select></div>
<div class="form-group"><label>Headers (JSON)</label><input type="text" id="whHeaders" placeholder='{"Authorization":"Bearer xxx"}'></div>
</div>
<button class="btn btn-success" onclick="addWebhook()"><i class="fas fa-plus"></i> Add</button>
<table class="table" style="margin-top:20px"><thead><tr><th>Name</th><th>URL</th><th>Method</th><th>OK</th><th>Err</th><th>Last</th><th>Actions</th></tr></thead><tbody id="webhooksTable"></tbody></table>
</div>
</div>
<div class="panel" id="panel-incoming">
<div class="panel-header"><h2>Incoming Webhooks</h2></div>
<div class="panel-body">
<p style="margin-bottom:16px;color:var(--text2)">Receive webhooks at:</p>
<div class="code-box"><?php echo htmlspecialchars($incomingUrl); ?></div>
<table class="table" style="margin-top:20px"><thead><tr><th>Endpoint</th><th>Method</th><th>IP</th><th>Body</th><th>Date</th></tr></thead><tbody id="incomingTable"></tbody></table>
</div>
</div>
<div class="panel" id="panel-logs">
<div class="panel-header"><h2>Logs</h2></div>
<div class="panel-body" style="padding:0"><table class="table"><thead><tr><th>Webhook</th><th>Code</th><th>Latency</th><th>Status</th><th>Date</th></tr></thead><tbody id="logsTable"></tbody></table></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
function showTab(t){document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));document.querySelectorAll('.tab').forEach(p=>p.classList.remove('active'));document.getElementById('panel-'+t).classList.add('active');document.querySelector('.tab[onclick*="'+t+'"]').classList.add('active');if(t==='incoming')loadIncoming();if(t==='logs')loadLogs();}
async function loadStats(){const d=await fetch('?action=stats').then(r=>r.json());document.getElementById('statWebhooks').textContent=d.webhooks;document.getElementById('statToday').textContent=d.today;document.getElementById('statSuccess').textContent=d.success;document.getElementById('statIncoming').textContent=d.incoming;}
async function loadWebhooks(){const d=await fetch('?action=list').then(r=>r.json());document.getElementById('webhooksTable').innerHTML=d.map(w=>'<tr><td>'+w.name+'</td><td style="max-width:150px;overflow:hidden;text-overflow:ellipsis">'+w.url+'</td><td>'+w.method+'</td><td style="color:var(--success)">'+w.success_count+'</td><td style="color:var(--danger)">'+w.error_count+'</td><td>'+(w.last_triggered?new Date(w.last_triggered).toLocaleString():'-')+'</td><td><button class="btn btn-sm btn-primary" onclick="testWH('+w.id+')"><i class="fas fa-play"></i></button> <button class="btn btn-sm btn-danger" onclick="delWH('+w.id+')"><i class="fas fa-trash"></i></button></td></tr>').join('')||'<tr><td colspan="7">No webhooks</td></tr>';}
async function addWebhook(){const fd=new FormData();fd.append('name',document.getElementById('whName').value);fd.append('url',document.getElementById('whUrl').value);fd.append('method',document.getElementById('whMethod').value);fd.append('headers',document.getElementById('whHeaders').value||'{}');await fetch('?action=add',{method:'POST',body:fd});toast('Added');loadWebhooks();loadStats();}
async function delWH(id){if(!confirm('Delete?'))return;await fetch('?action=delete',{method:'POST',body:new URLSearchParams({id})});toast('Deleted');loadWebhooks();loadStats();}
async function testWH(id){const payload=prompt('JSON payload:','{"test":true}');if(!payload)return;toast('Triggering...');const fd=new FormData();fd.append('id',id);fd.append('payload',payload);const r=await fetch('?action=trigger',{method:'POST',body:fd}).then(r=>r.json());toast(r.success?'OK: '+r.code+' ('+r.latency+'ms)':'Error: '+r.code);loadWebhooks();loadStats();}
async function loadIncoming(){const d=await fetch('?action=incoming_logs').then(r=>r.json());document.getElementById('incomingTable').innerHTML=d.map(l=>'<tr><td>'+l.endpoint+'</td><td>'+l.method+'</td><td>'+l.ip+'</td><td style="max-width:150px;overflow:hidden">'+(l.body||'-').substring(0,40)+'</td><td>'+new Date(l.created_at).toLocaleString()+'</td></tr>').join('')||'<tr><td colspan="5">No data</td></tr>';}
async function loadLogs(){const d=await fetch('?action=logs').then(r=>r.json());document.getElementById('logsTable').innerHTML=d.map(l=>'<tr><td>'+(l.webhook_name||'-')+'</td><td>'+l.response_code+'</td><td>'+l.latency_ms+'ms</td><td><span class="badge badge-'+(l.status==='success'?'success':'danger')+'">'+l.status+'</span></td><td>'+new Date(l.created_at).toLocaleString()+'</td></tr>').join('')||'<tr><td colspan="5">No logs</td></tr>';}
function toast(msg){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),3000);}
loadStats();loadWebhooks();
</script>
</body>
</html>