985 lines
49 KiB
HTML
Executable File
985 lines
49 KiB
HTML
Executable File
<?php include_once("/opt/wevads-arsenal/public/api/wevads-metrics.php"); ?>
|
|
<?php @$_db=new PDO("pgsql:host=localhost;dbname=adx_system","admin","admin123");$_db->exec("SET search_path TO admin");$_acf_t=(int)$_db->query("SELECT COUNT(*) FROM scrapping_results")->fetchColumn();$_acf_a=(int)$_db->query("SELECT COUNT(*) FROM dark_scraper_jobs WHERE status='running'")->fetchColumn();$_acf_v=(int)$_db->query("SELECT COUNT(*) FROM scrapping_results WHERE is_verified=true")->fetchColumn();$_acf_r=$_acf_t>0?round($_acf_v/$_acf_t*100,1):0;$_acf_td=(int)$_db->query("SELECT COUNT(*) FROM scrapping_results WHERE extracted_at::date=CURRENT_DATE")->fetchColumn();?>
|
|
<!DOCTYPE html><html lang="en"><head>
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
<title>WEVADS - Advanced Craping Factory — Arsenal</title>
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
:root{--bg:#060a14;--s:#0c1220;--c:#111827;--cy:#22d3ee;--gn:#10b981;--rd:#ef4444;--or:#f59e0b;--pu:#a78bfa;--tx:#e2e8f0;--t2:#94a3b8}
|
|
body{background:#060a14;color:var(--tx);font-family:'DM Sans',sans-serif;padding:24px}
|
|
.hd{font-size:22px;font-weight:700;margin-bottom:20px;display:flex;align-items:center;gap:10px}
|
|
.hd span{font-size:26px}
|
|
.g4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:24px}
|
|
.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:24px}
|
|
.g2{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:24px}
|
|
.cd{background:var(--s);border:1px solid rgba(34,211,238,.08);border-radius:12px;padding:20px}
|
|
.cd h3{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--t2);margin-bottom:8px}
|
|
.sv{font-size:28px;font-weight:700;font-family:'JetBrains Mono',monospace}
|
|
.badge{display:inline-block;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:600}
|
|
.badge.gn{background:rgba(16,185,129,.15);color:var(--gn)}
|
|
.badge.rd{background:rgba(239,68,68,.15);color:var(--rd)}
|
|
.badge.or{background:rgba(245,158,11,.15);color:var(--or)}
|
|
.badge.cy{background:rgba(34,211,238,.15);color:var(--cy)}
|
|
.badge.pu{background:rgba(167,139,250,.15);color:var(--pu)}
|
|
table{width:100%;border-collapse:collapse}
|
|
th{text-align:left;padding:12px;font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--t2);border-bottom:1px solid rgba(30,41,59,.5)}
|
|
td{padding:10px 12px;border-bottom:1px solid rgba(30,41,59,.3);font-size:13px;font-family:'JetBrains Mono',monospace}
|
|
tr:hover{background:rgba(34,211,238,.03)}
|
|
.btn{padding:8px 18px;border-radius:8px;border:none;cursor:pointer;font-size:13px;font-weight:600;transition:.2s}
|
|
.btn-cy{background:rgba(34,211,238,.15);color:var(--cy)}
|
|
.btn-cy:hover{background:rgba(34,211,238,.25)}
|
|
.btn-gn{background:rgba(16,185,129,.15);color:var(--gn)}
|
|
.btn-rd{background:rgba(239,68,68,.15);color:var(--rd)}
|
|
.bar{height:6px;border-radius:3px;background:rgba(30,41,59,.5);overflow:hidden;margin-top:6px}
|
|
.bar div{height:100%;border-radius:3px;transition:width .5s}
|
|
.log{background:var(--bg);border-radius:8px;padding:12px;max-height:200px;overflow-y:auto;font-family:'JetBrains Mono',monospace;font-size:12px;line-height:1.8}
|
|
.act{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap}
|
|
.modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.8);display:none;align-items:center;justify-content:center;z-index:1000}
|
|
.modal-content{background:var(--s);border-radius:12px;padding:30px;width:90%;max-width:1000px;border:1px solid rgba(34,211,238,.15)}
|
|
.input-group{margin-bottom:15px}
|
|
.input-group label{display:block;margin-bottom:8px;font-size:13px;color:var(--t2)}
|
|
.input-group input,.input-group select,.input-group textarea{width:100%;padding:10px;background:var(--bg);border:1px solid rgba(30,41,59,.5);border-radius:6px;color:var(--tx);font-family:'JetBrains Mono',monospace}
|
|
.checkbox-group{display:flex;flex-wrap:wrap;gap:15px;margin:10px 0}
|
|
.checkbox-item{display:flex;align-items:center;gap:8px}
|
|
.checkbox-item input{width:auto}
|
|
.tabs{display:flex;gap:2px;margin-bottom:20px}
|
|
.tab{flex:1;padding:12px;text-align:center;background:var(--bg);border:1px solid rgba(30,41,59,.5);cursor:pointer;font-size:13px;font-weight:600}
|
|
.tab.active{background:var(--s);border-color:var(--cy);color:var(--cy)}
|
|
.tab-content{display:none}
|
|
.tab-content.active{display:block}
|
|
.scraping-status{padding:10px;border-radius:6px;margin:10px 0;display:none}
|
|
.chart-container{height:80px;margin-top:10px;display:flex;align-items:flex-end;gap:2px}
|
|
.chart-bar{background:var(--cy);flex:1;border-radius:2px 2px 0 0;min-height:1px}
|
|
@media(max-width:1200px){.g4{grid-template-columns:repeat(2,1fr)}}
|
|
@media(max-width:768px){.g4,.g3,.g2{grid-template-columns:1fr}}
|
|
.wv-status{position:fixed;top:12px;right:140px;z-index:9998;background:rgba(52,211,153,.15);border:1px solid #34d399;border-radius:12px;padding:3px 10px;color:#34d399;font-size:10px;font-weight:700;font-family:'JetBrains Mono',monospace}
|
|
|
|
.sc,.card,[class*="stat-card"]{transition:all .25s ease;position:relative;overflow:hidden}
|
|
.sc:hover,.card:hover,[class*="stat-card"]:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.25)}
|
|
.sc::after,.card::after{content:'';position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--cy,#22d3ee);opacity:0;transition:opacity .25s}
|
|
.sc:hover::after,.card:hover::after{opacity:.7}
|
|
.btn,.button,[class*="btn-"]{transition:all .2s ease}
|
|
.btn:hover,.button:hover{transform:translateY(-1px)}
|
|
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
.sc,.card{animation:fadeIn .4s ease both}
|
|
.sc:nth-child(2),.card:nth-child(2){animation-delay:.05s}
|
|
.sc:nth-child(3),.card:nth-child(3){animation-delay:.1s}
|
|
.sc:nth-child(4),.card:nth-child(4){animation-delay:.15s}
|
|
.sc:nth-child(5),.card:nth-child(5){animation-delay:.2s}
|
|
.sc:nth-child(6),.card:nth-child(6){animation-delay:.25s}
|
|
</style>
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="wevads-global.css?v1770777318">
|
|
</head><body>
|
|
|
|
|
|
<div class="hd"><span>🎯</span> Advanced Craping Factory</div>
|
|
|
|
<div class="g4">
|
|
<div class="cd" style="cursor:pointer" onclick="drillDown('total')">
|
|
<h3>Total Scraped <span style="font-size:9px;color:var(--cy)">▼ drill</span></h3>
|
|
<div class="sv" style="color:var(--cy)" id="kpiTotal"><?=number_format($_acf_t)?></div>
|
|
<div style="font-size:12px;color:var(--t2)">emails collected</div>
|
|
<div class="chart-container" id="chartTotal"></div>
|
|
</div>
|
|
<div class="cd" style="cursor:pointer" onclick="drillDown('active')">
|
|
<h3>Active Jobs <span style="font-size:9px;color:var(--gn)">▼ drill</span></h3>
|
|
<div class="sv" style="color:var(--gn)" id="kpiActive"><?=$_acf_a?></div>
|
|
<div style="font-size:12px;color:var(--t2)">running now</div>
|
|
<div class="chart-container" id="chartActive"></div>
|
|
</div>
|
|
<div class="cd" style="cursor:pointer" onclick="drillDown('success')">
|
|
<h3>Success Rate <span style="font-size:9px;color:var(--or)">▼ drill</span></h3>
|
|
<div class="sv" style="color:var(--or)" id="kpiSuccess"><?=$_acf_r?>%</div>
|
|
<div class="bar"><div id="successBar" style="width:0%;background:var(--or)"></div></div>
|
|
</div>
|
|
<div class="cd" style="cursor:pointer" onclick="drillDown('today')">
|
|
<h3>Today <span style="font-size:9px;color:var(--pu)">▼ drill</span></h3>
|
|
<div class="sv" style="color:var(--pu)" id="kpiToday"><?=$_acf_td?></div>
|
|
<div style="font-size:12px;color:var(--t2)">new emails</div>
|
|
<div class="chart-container" id="chartToday"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Drill-down Modal -->
|
|
<div class="modal" id="drillModal">
|
|
<div class="modal-content" style="max-height:80vh;overflow-y:auto">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">
|
|
<h3 id="drillTitle" style="color:var(--cy);font-size:16px">Drill Down</h3>
|
|
<button class="btn btn-rd" onclick="document.getElementById('drillModal').style.display='none'" style="padding:5px 15px">✕</button>
|
|
</div>
|
|
<div id="drillContent" style="font-family:'JetBrains Mono',monospace;font-size:13px">Loading...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tabs">
|
|
<div class="tab active" onclick="switchTab(1)">🎯 Manual Scraping</div>
|
|
<div class="tab" onclick="switchTab(2)">📋 Job History</div>
|
|
<div class="tab" onclick="switchTab(3)">📤 Export Results</div>
|
|
</div>
|
|
|
|
<div class="tab-content active" id="tab1">
|
|
<div class="cd">
|
|
<h3>🎯 Target Configuration</h3>
|
|
|
|
<div class="g2" style="margin-bottom:20px">
|
|
<div class="input-group">
|
|
<label>🔍 Keywords / Job Titles</label>
|
|
<input type="text" id="keywords" placeholder="CEO, Marketing Director, CTO, Founder, etc.">
|
|
</div>
|
|
<div class="input-group">
|
|
<label>🏢 Industry / Sector</label>
|
|
<select id="industry">
|
|
<option value="">All Industries</option>
|
|
<optgroup label="💊 Health & Pharma">
|
|
<option value="Pharma">Pharmaceuticals</option>
|
|
<option value="Biotech">Biotech / Life Sciences</option>
|
|
<option value="MedDevice">Medical Devices</option>
|
|
<option value="Healthcare">Healthcare / Hospitals</option>
|
|
<option value="Dental">Dental / Optical</option>
|
|
<option value="Wellness">Wellness / Supplements / Nutra</option>
|
|
<option value="Fitness">Fitness / Sports</option>
|
|
</optgroup>
|
|
<optgroup label="💻 Tech & Digital">
|
|
<option value="IT">IT / Software / SaaS</option>
|
|
<option value="HighTech">HighTech / AI / Cloud</option>
|
|
<option value="Cybersecurity">Cybersecurity</option>
|
|
<option value="Telecom">Telecom / ISP</option>
|
|
<option value="Gaming">Gaming / Entertainment</option>
|
|
<option value="Crypto">Crypto / Blockchain / Fintech</option>
|
|
</optgroup>
|
|
<optgroup label="🏦 Finance & Insurance">
|
|
<option value="Finance">Finance / Banking</option>
|
|
<option value="Insurance">Insurance</option>
|
|
<option value="Trading">Trading / Investment</option>
|
|
<option value="Lending">Lending / Credit / Loans</option>
|
|
<option value="Accounting">Accounting / Tax</option>
|
|
</optgroup>
|
|
<optgroup label="🛒 Retail & E-commerce">
|
|
<option value="Ecommerce">E-commerce</option>
|
|
<option value="Retail">Retail / FMCG</option>
|
|
<option value="Fashion">Fashion / Beauty / Cosmetics</option>
|
|
<option value="Food">Food & Beverage</option>
|
|
<option value="Luxury">Luxury Goods</option>
|
|
<option value="HomeGarden">Home & Garden</option>
|
|
</optgroup>
|
|
<optgroup label="🏢 Business Services">
|
|
<option value="Marketing">Marketing / Advertising / SEO</option>
|
|
<option value="Consulting">Consulting / Strategy</option>
|
|
<option value="Legal">Legal Services</option>
|
|
<option value="HR">HR / Recruitment / Staffing</option>
|
|
<option value="Education">Education / E-learning</option>
|
|
<option value="Media">Media / Publishing / PR</option>
|
|
</optgroup>
|
|
<optgroup label="🏗️ Industry & Property">
|
|
<option value="RealEstate">Real Estate / Property</option>
|
|
<option value="Construction">Construction / Architecture</option>
|
|
<option value="Manufacturing">Manufacturing / Industrial</option>
|
|
<option value="Energy">Energy / Oil & Gas / Solar</option>
|
|
<option value="Automotive">Automotive</option>
|
|
<option value="Logistics">Logistics / Transport / Supply Chain</option>
|
|
<option value="Agriculture">Agriculture</option>
|
|
</optgroup>
|
|
<optgroup label="🎰 Gambling & Dating">
|
|
<option value="iGaming">iGaming / Casino / Betting</option>
|
|
<option value="Sweepstakes">Sweepstakes / Contests</option>
|
|
<option value="Dating">Dating / Social</option>
|
|
<option value="Adult">Adult Entertainment</option>
|
|
</optgroup>
|
|
<optgroup label="🏛️ Government & NGO">
|
|
<option value="Government">Government / Public Sector</option>
|
|
<option value="NGO">NGO / Non-profit</option>
|
|
<option value="Military">Military / Defense</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g2" style="margin-bottom:20px">
|
|
<div class="input-group">
|
|
<label>🌍 Country</label>
|
|
<select id="country">
|
|
<option value="">All Countries</option>
|
|
<optgroup label="🔥 Top Markets">
|
|
<option value="DE">🇩🇪 Germany</option>
|
|
<option value="FR">🇫🇷 France</option>
|
|
<option value="UK">🇬🇧 United Kingdom</option>
|
|
<option value="US">🇺🇸 USA</option>
|
|
<option value="CA">🇨🇦 Canada</option>
|
|
<option value="NL">🇳🇱 Netherlands</option>
|
|
<option value="IT">🇮🇹 Italy</option>
|
|
<option value="ES">🇪🇸 Spain</option>
|
|
<option value="CH">🇨🇭 Switzerland</option>
|
|
</optgroup>
|
|
<optgroup label="🌍 Europe">
|
|
<option value="BE">🇧🇪 Belgium</option>
|
|
<option value="AT">🇦🇹 Austria</option>
|
|
<option value="DK">🇩🇰 Denmark</option>
|
|
<option value="SE">🇸🇪 Sweden</option>
|
|
<option value="NO">🇳🇴 Norway</option>
|
|
<option value="FI">🇫🇮 Finland</option>
|
|
<option value="PT">🇵🇹 Portugal</option>
|
|
<option value="PL">🇵🇱 Poland</option>
|
|
<option value="CZ">🇨🇿 Czech Republic</option>
|
|
<option value="IE">🇮🇪 Ireland</option>
|
|
<option value="RO">🇷🇴 Romania</option>
|
|
<option value="HU">🇭🇺 Hungary</option>
|
|
<option value="GR">🇬🇷 Greece</option>
|
|
<option value="HR">🇭🇷 Croatia</option>
|
|
<option value="BG">🇧🇬 Bulgaria</option>
|
|
</optgroup>
|
|
<optgroup label="🌍 Africa & Middle East">
|
|
<option value="MA">🇲🇦 Morocco</option>
|
|
<option value="TN">🇹🇳 Tunisia</option>
|
|
<option value="DZ">🇩🇿 Algeria</option>
|
|
<option value="EG">🇪🇬 Egypt</option>
|
|
<option value="ZA">🇿🇦 South Africa</option>
|
|
<option value="NG">🇳🇬 Nigeria</option>
|
|
<option value="AE">🇦🇪 UAE</option>
|
|
<option value="SA">🇸🇦 Saudi Arabia</option>
|
|
</optgroup>
|
|
<optgroup label="🌏 Asia-Pacific">
|
|
<option value="AU">🇦🇺 Australia</option>
|
|
<option value="NZ">🇳🇿 New Zealand</option>
|
|
<option value="JP">🇯🇵 Japan</option>
|
|
<option value="SG">🇸🇬 Singapore</option>
|
|
<option value="IN">🇮🇳 India</option>
|
|
<option value="PH">🇵🇭 Philippines</option>
|
|
</optgroup>
|
|
<optgroup label="🌎 Americas">
|
|
<option value="MX">🇲🇽 Mexico</option>
|
|
<option value="BR">🇧🇷 Brazil</option>
|
|
<option value="CO">🇨🇴 Colombia</option>
|
|
<option value="AR">🇦🇷 Argentina</option>
|
|
<option value="CL">🇨🇱 Chile</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
<div class="input-group">
|
|
<label>📍 City / Region</label>
|
|
<input type="text" id="city" placeholder="Berlin, Paris, London, Casablanca, Tunis, Toronto, Milan, Dubai">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g2" style="margin-bottom:20px">
|
|
<div class="input-group">
|
|
<label>🏢 Company Size</label>
|
|
<select id="companySize">
|
|
<option value="">Any Size</option>
|
|
<option value="1-10">1-10 employees</option>
|
|
<option value="11-50">11-50 employees</option>
|
|
<option value="51-200">51-200 employees</option>
|
|
<option value="201-500">201-500 employees</option>
|
|
<option value="501-1000">501-1000 employees</option>
|
|
<option value="1000+">1000+ employees</option>
|
|
</select>
|
|
</div>
|
|
<div class="input-group">
|
|
<label>💰 Revenue Range (optional)</label>
|
|
<select id="revenue">
|
|
<option value="">Any Revenue</option>
|
|
<option value="<1M">< $1M</option>
|
|
<option value="1-10M">$1M - $10M</option>
|
|
<option value="10-50M">$10M - $50M</option>
|
|
<option value="50-100M">$50M - $100M</option>
|
|
<option value="100M+">$100M+</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<label>🎯 Target Domains (optional, comma separated)</label>
|
|
<textarea id="domains" rows="2" placeholder="example.com, company.com, startup.io"></textarea>
|
|
</div>
|
|
|
|
<h3 style="margin-top:25px;margin-bottom:15px">🔧 Scraping Sources</h3>
|
|
<div style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--cy);margin-bottom:8px;font-weight:600">🏢 B2B Sources</div>
|
|
<div class="checkbox-group">
|
|
<div class="checkbox-item"><input type="checkbox" id="source_linkedin" checked><label for="source_linkedin">LinkedIn</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_hunter" checked><label for="source_hunter">Hunter.io</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_apollo" checked><label for="source_apollo">Apollo.io</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_rocketreach"><label for="source_rocketreach">RocketReach</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_zoominfo"><label for="source_zoominfo">ZoomInfo</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_crunchbase"><label for="source_crunchbase">CrunchBase</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_clearbit"><label for="source_clearbit">Clearbit</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_lusha"><label for="source_lusha">Lusha</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_snov"><label for="source_snov">Snov.io</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_dropcontact"><label for="source_dropcontact">Dropcontact</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_kaspr"><label for="source_kaspr">Kaspr</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_phantombuster"><label for="source_phantombuster">PhantomBuster</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_salesql"><label for="source_salesql">SalesQL</label></div>
|
|
</div>
|
|
<div style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--or);margin-bottom:8px;margin-top:12px;font-weight:600">🛒 B2C Sources</div>
|
|
<div class="checkbox-group">
|
|
<div class="checkbox-item"><input type="checkbox" id="source_facebook"><label for="source_facebook">Facebook Groups</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_instagram"><label for="source_instagram">Instagram</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_twitter"><label for="source_twitter">X / Twitter</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_tiktok"><label for="source_tiktok">TikTok Profiles</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_telegram"><label for="source_telegram">Telegram Channels</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_whois"><label for="source_whois">WHOIS Domains</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_yellowpages"><label for="source_yellowpages">Yellow Pages</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_yelp"><label for="source_yelp">Yelp / TripAdvisor</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_shopify"><label for="source_shopify">Shopify Stores</label></div>
|
|
</div>
|
|
<div style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--pu);margin-bottom:8px;margin-top:12px;font-weight:600">🌐 Web & Tech Sources</div>
|
|
<div class="checkbox-group">
|
|
<div class="checkbox-item"><input type="checkbox" id="source_github"><label for="source_github">GitHub</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_angellist"><label for="source_angellist">AngelList / Wellfound</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_producthunt"><label for="source_producthunt">ProductHunt</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_stackoverflow"><label for="source_stackoverflow">StackOverflow</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_reddit"><label for="source_reddit">Reddit</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_forums"><label for="source_forums">Forums / Boards</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_googlemaps"><label for="source_googlemaps">Google Maps / GMB</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="source_webscrape"><label for="source_webscrape">Custom URL Scrape</label></div>
|
|
</div>
|
|
|
|
<div class="g2" style="margin-top:20px">
|
|
<div class="input-group">
|
|
<label>📊 Max Results</label>
|
|
<select id="maxResults">
|
|
<option value="100">100</option>
|
|
<option value="250" selected>250</option>
|
|
<option value="500">500</option>
|
|
<option value="1000">1000</option>
|
|
<option value="5000">5000</option>
|
|
</select>
|
|
</div>
|
|
<div class="input-group">
|
|
<label>⚡ Speed</label>
|
|
<select id="speed">
|
|
<option value="slow">Slow (stealth)</option>
|
|
<option value="medium" selected>Medium</option>
|
|
<option value="fast">Fast</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="act" style="margin-top:25px">
|
|
<button class="btn btn-gn" onclick="startScraping()" id="startBtn">🚀 Start Scraping</button>
|
|
<button class="btn btn-cy" onclick="previewSearch()">👁️ Preview Search</button>
|
|
<button class="btn btn-rd" onclick="clearForm()">🗑️ Clear Form</button>
|
|
</div>
|
|
|
|
<div id="scrapingStatus" class="scraping-status" style="display:none">
|
|
<div style="display:flex;align-items:center;gap:10px">
|
|
<div class="bar" style="flex:1"><div id="progressBar" style="width:0%;background:var(--cy)"></div></div>
|
|
<span id="progressText" style="font-family:'JetBrains Mono'">0%</span>
|
|
</div>
|
|
<div id="statusMessage" style="margin-top:8px;font-size:12px">Initializing...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content" id="tab2">
|
|
<div class="cd">
|
|
<h3>📋 Scraping Job History</h3>
|
|
<div class="act">
|
|
<select id="filterStatus" style="padding:8px 12px;background:var(--bg);border:1px solid rgba(30,41,59,.5);border-radius:6px;color:var(--tx);font-family:'JetBrains Mono',monospace">
|
|
<option value="">All Status</option>
|
|
<option value="running">Running</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="failed">Failed</option>
|
|
<option value="paused">Paused</option>
|
|
</select>
|
|
<button class="btn btn-cy" onclick="loadJobs()">🔄 Refresh</button>
|
|
<button class="btn btn-rd" onclick="stopAllJobs()">⏹️ Stop All</button>
|
|
</div>
|
|
<table>
|
|
<thead><tr><th>ID</th><th>Keywords</th><th>Country</th><th>Results</th><th>Status</th><th>Started</th><th>Actions</th></tr></thead>
|
|
<tbody id="jobsTable">
|
|
<tr><td colspan="7" style="text-align:center;padding:40px;color:var(--t2)">Loading...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content" id="tab3">
|
|
<div class="cd">
|
|
<h3>📤 Export Scraped Results</h3>
|
|
<div class="g2" style="margin-bottom:20px">
|
|
<div class="input-group">
|
|
<label>📅 Date Range</label>
|
|
<div style="display:flex;gap:10px">
|
|
<input type="date" id="exportFrom" style="flex:1">
|
|
<input type="date" id="exportTo" style="flex:1">
|
|
</div>
|
|
</div>
|
|
<div class="input-group">
|
|
<label>🎯 Filter by Country</label>
|
|
<select id="exportCountry">
|
|
<option value="">All Countries</option>
|
|
<option value="DE">Germany</option>
|
|
<option value="FR">France</option>
|
|
<option value="UK">UK</option>
|
|
<option value="US">USA</option>
|
|
<option value="CA">Canada</option>
|
|
<option value="NL">Netherlands</option>
|
|
<option value="IT">Italy</option>
|
|
<option value="ES">Spain</option>
|
|
<option value="CH">Switzerland</option>
|
|
<option value="BE">Belgium</option>
|
|
<option value="AT">Austria</option>
|
|
<option value="SE">Sweden</option>
|
|
<option value="NO">Norway</option>
|
|
<option value="DK">Denmark</option>
|
|
<option value="FI">Finland</option>
|
|
<option value="PT">Portugal</option>
|
|
<option value="PL">Poland</option>
|
|
<option value="CZ">Czech Republic</option>
|
|
<option value="IE">Ireland</option>
|
|
<option value="MA">Morocco</option>
|
|
<option value="TN">Tunisia</option>
|
|
<option value="DZ">Algeria</option>
|
|
<option value="EG">Egypt</option>
|
|
<option value="ZA">South Africa</option>
|
|
<option value="AE">UAE</option>
|
|
<option value="AU">Australia</option>
|
|
<option value="NZ">New Zealand</option>
|
|
<option value="MX">Mexico</option>
|
|
<option value="BR">Brazil</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<label>📝 Columns to Export</label>
|
|
<div class="checkbox-group">
|
|
<div class="checkbox-item"><input type="checkbox" id="col_email" checked><label for="col_email">Email</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="col_name" checked><label for="col_name">Name</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="col_title" checked><label for="col_title">Job Title</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="col_company"><label for="col_company">Company</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="col_location"><label for="col_location">Location</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="col_industry"><label for="col_industry">Industry</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="col_source"><label for="col_source">Source</label></div>
|
|
<div class="checkbox-item"><input type="checkbox" id="col_date"><label for="col_date">Scraped Date</label></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<label>📁 Export Format</label>
|
|
<div style="display:flex;gap:15px">
|
|
<div class="checkbox-item"><input type="radio" id="format_csv" name="format" checked><label for="format_csv">CSV</label></div>
|
|
<div class="checkbox-item"><input type="radio" id="format_json" name="format"><label for="format_json">JSON</label></div>
|
|
<div class="checkbox-item"><input type="radio" id="format_txt" name="format"><label for="format_txt">TXT (emails only)</label></div>
|
|
<div class="checkbox-item"><input type="radio" id="format_xlsx" name="format"><label for="format_xlsx">Excel</label></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="act" style="margin-top:25px">
|
|
<button class="btn btn-gn" onclick="exportResults()">📥 Export Results</button>
|
|
<button class="btn btn-cy" onclick="previewExport()">👁️ Preview Data</button>
|
|
<button class="btn btn-rd" onclick="deleteOldData()">🗑️ Delete Old Data</button>
|
|
</div>
|
|
|
|
<div id="exportPreview" style="margin-top:20px;display:none">
|
|
<h4>Preview (first 10 rows)</h4>
|
|
<div class="log" id="previewData" style="max-height:200px"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cd" style="margin-top:20px">
|
|
<h3>📊 Live Scraping Log</h3>
|
|
<div class="log" id="scrapingLog">
|
|
<div style="color:var(--t2)">Waiting for scraping activity...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API = '/api/advanced-scraping.php';
|
|
let activeJobId = null;
|
|
let progressInterval = null;
|
|
|
|
async function loadStats() {
|
|
try {
|
|
const r = await fetch(API + '?action=stats');
|
|
const d = await r.json();
|
|
if(d.status === 'success' && d.data) {
|
|
const s = d.data;
|
|
document.getElementById('kpiTotal').textContent = (s.total || 0).toLocaleString();
|
|
document.getElementById('kpiActive').textContent = s.active_jobs || 0;
|
|
document.getElementById('kpiSuccess').textContent = (s.success_rate || 0) + '%';
|
|
document.getElementById('kpiToday').textContent = s.today || 0;
|
|
|
|
document.getElementById('successBar').style.width = (s.success_rate || 0) + '%';
|
|
document.getElementById('successBar').style.background =
|
|
(s.success_rate >= 90) ? 'var(--gn)' : (s.success_rate >= 70) ? 'var(--or)' : 'var(--rd)';
|
|
}
|
|
} catch(e) { /* stats retry */ }
|
|
}
|
|
|
|
async function loadJobs() {
|
|
const status = document.getElementById('filterStatus').value;
|
|
try {
|
|
const url = API + '?action=jobs' + (status ? '&status=' + encodeURIComponent(status) : '');
|
|
const r = await fetch(url);
|
|
const d = await r.json();
|
|
const jobs = d.jobs || d.data || [];
|
|
|
|
const tbody = document.getElementById('jobsTable');
|
|
tbody.innerHTML = '';
|
|
|
|
if(jobs.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:40px;color:var(--t2)">No jobs found</td></tr>';
|
|
} else {
|
|
jobs.forEach(job => {
|
|
const statusBadge = job.status === 'completed' ? 'badge gn' :
|
|
job.status === 'running' ? 'badge cy' :
|
|
job.status === 'failed' ? 'badge rd' : 'badge or';
|
|
const statusText = job.status ? job.status.charAt(0).toUpperCase() + job.status.slice(1) : 'Unknown';
|
|
|
|
const resultsColor = (job.results_found || job.emails_found || 0) > 0 ? 'var(--gn)' : 'var(--t2)';
|
|
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td><span style="font-family:JetBrains Mono">${job.id || 'N/A'}</span></td>
|
|
<td>${job.keywords || job.source_url || '-'}</td>
|
|
<td>${job.country || 'All'}</td>
|
|
<td><span style="color:${resultsColor}">${job.results_found || job.emails_found || 0}</span></td>
|
|
<td><span class="${statusBadge}">${statusText}</span></td>
|
|
<td>${(job.started_at || job.created_at) ? new Date(job.started_at || job.created_at).toLocaleString() : '-'}</td>
|
|
<td>
|
|
${job.status === 'running' ?
|
|
`<button class="btn" style="padding:4px 8px;font-size:11px" onclick="stopJob('${job.id}')">Stop</button>` :
|
|
`<button class="btn" style="padding:4px 8px;font-size:11px" onclick="deleteJob('${job.id}')">Delete</button>`}
|
|
${(job.results_found || job.emails_found || 0) > 0 ?
|
|
`<button class="btn" style="padding:4px 8px;font-size:11px" onclick="exportJob('${job.id}')">Export</button>` : ''}
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
}
|
|
} catch(e) {
|
|
/* jobs retry */
|
|
document.getElementById('jobsTable').innerHTML =
|
|
'<tr><td colspan="7" style="text-align:center;padding:40px;color:var(--rd)">Error loading jobs</td></tr>';
|
|
}
|
|
}
|
|
|
|
async function startScraping() {
|
|
const keywords = document.getElementById('keywords').value.trim();
|
|
if(!keywords) {
|
|
alert('Please enter keywords to search for');
|
|
return;
|
|
}
|
|
|
|
const sources = collectSources();
|
|
|
|
if(sources.length === 0) {
|
|
alert('Please select at least one scraping source');
|
|
return;
|
|
}
|
|
|
|
const config = {
|
|
action: 'scrape',
|
|
keywords: keywords,
|
|
industry: document.getElementById('industry').value,
|
|
country: document.getElementById('country').value,
|
|
city: document.getElementById('city').value.trim(),
|
|
company_size: document.getElementById('companySize').value,
|
|
revenue: document.getElementById('revenue').value,
|
|
domains: document.getElementById('domains').value.split(',').map(d => d.trim()).filter(d => d),
|
|
sources: sources,
|
|
max_results: parseInt(document.getElementById('maxResults').value),
|
|
speed: document.getElementById('speed').value
|
|
};
|
|
|
|
const btn = document.getElementById('startBtn');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '⏳ Starting...';
|
|
|
|
document.getElementById('scrapingStatus').style.display = 'block';
|
|
document.getElementById('statusMessage').textContent = 'Starting scraping job...';
|
|
document.getElementById('progressBar').style.width = '5%';
|
|
document.getElementById('progressText').textContent = '5%';
|
|
|
|
try {
|
|
const r = await fetch(API, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(config)
|
|
});
|
|
const d = await r.json();
|
|
|
|
if(d.status === 'success') {
|
|
activeJobId = d.job_id;
|
|
document.getElementById('statusMessage').textContent = `Job started! ID: ${activeJobId}`;
|
|
document.getElementById('progressBar').style.width = '10%';
|
|
document.getElementById('progressText').textContent = '10%';
|
|
|
|
// Start polling progress
|
|
progressInterval = setInterval(checkProgress, 3000);
|
|
|
|
// Switch to jobs tab
|
|
switchTab(2);
|
|
setTimeout(loadJobs, 1000);
|
|
|
|
alert(`✅ Scraping job started! Job ID: ${activeJobId}`);
|
|
} else {
|
|
alert(`❌ Failed: ${d.message || 'Unknown error'}`);
|
|
resetUI();
|
|
}
|
|
} catch(e) {
|
|
alert(`❌ Error: ${e.message}`);
|
|
resetUI();
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '🚀 Start Scraping';
|
|
}
|
|
}
|
|
|
|
async function checkProgress() {
|
|
if(!activeJobId) return;
|
|
|
|
try {
|
|
const r = await fetch(API + '?action=progress&job_id=' + activeJobId);
|
|
const d = await r.json();
|
|
|
|
if(d.status === 'success' && d.data) {
|
|
const p = d.data;
|
|
const progress = p.progress || 0;
|
|
|
|
document.getElementById('progressBar').style.width = progress + '%';
|
|
document.getElementById('progressText').textContent = progress + '%';
|
|
document.getElementById('statusMessage').textContent =
|
|
`Processing... ${p.current_source || ''} | Found: ${p.emails_found || 0} emails`;
|
|
|
|
// Update log
|
|
updateLog(p.logs || []);
|
|
|
|
// If job completed
|
|
if(p.status === 'completed' || p.status === 'failed') {
|
|
clearInterval(progressInterval);
|
|
activeJobId = null;
|
|
document.getElementById('statusMessage').textContent =
|
|
`Job ${p.status}! Found ${p.emails_found || 0} emails.`;
|
|
|
|
// Refresh stats and jobs
|
|
loadStats();
|
|
loadJobs();
|
|
}
|
|
}
|
|
} catch(e) {
|
|
/* progress retry */
|
|
}
|
|
}
|
|
|
|
function updateLog(logs) {
|
|
const logDiv = document.getElementById('scrapingLog');
|
|
if(!logs.length) return;
|
|
|
|
logDiv.innerHTML = '';
|
|
logs.slice(-20).reverse().forEach(log => {
|
|
const time = log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : new Date().toLocaleTimeString();
|
|
const color = log.level === 'success' ? 'var(--gn)' :
|
|
log.level === 'warning' ? 'var(--or)' :
|
|
log.level === 'error' ? 'var(--rd)' : 'var(--t2)';
|
|
|
|
logDiv.innerHTML += `<div style="color:${color}">[${time}] ${log.message || ''}</div>`;
|
|
});
|
|
|
|
logDiv.scrollTop = logDiv.scrollHeight;
|
|
}
|
|
|
|
async function stopJob(jobId) {
|
|
if(!confirm('Stop this scraping job?')) return;
|
|
|
|
try {
|
|
const r = await fetch(API, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({action: 'stop', job_id: jobId})
|
|
});
|
|
const d = await r.json();
|
|
|
|
if(d.status === 'success') {
|
|
alert('✅ Job stopped');
|
|
loadJobs();
|
|
} else {
|
|
alert(`❌ Failed: ${d.message}`);
|
|
}
|
|
} catch(e) {
|
|
alert(`❌ Error: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
async function exportJob(jobId) {
|
|
try {
|
|
const r = await fetch(API + '?action=export_job&job_id=' + jobId);
|
|
if(r.ok) {
|
|
const blob = await r.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `scraping-job-${jobId}-${new Date().toISOString().split('T')[0]}.csv`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
window.URL.revokeObjectURL(url);
|
|
} else {
|
|
alert('❌ Failed to export job results');
|
|
}
|
|
} catch(e) {
|
|
alert(`❌ Error: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
async function stopAllJobs() {
|
|
if(!confirm('Stop ALL active scraping jobs?')) return;
|
|
|
|
try {
|
|
const r = await fetch(API, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({action: 'stop_all'})
|
|
});
|
|
const d = await r.json();
|
|
|
|
if(d.status === 'success') {
|
|
alert(`✅ Stopped ${d.count || 0} jobs`);
|
|
loadJobs();
|
|
loadStats();
|
|
} else {
|
|
alert(`❌ Failed: ${d.message}`);
|
|
}
|
|
} catch(e) {
|
|
alert(`❌ Error: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
async function previewSearch() {
|
|
const keywords = document.getElementById('keywords').value.trim();
|
|
if(!keywords) {
|
|
alert('Enter keywords first');
|
|
return;
|
|
}
|
|
|
|
const config = {
|
|
keywords: keywords,
|
|
country: document.getElementById('country').value,
|
|
city: document.getElementById('city').value.trim(),
|
|
industry: document.getElementById('industry').value
|
|
};
|
|
|
|
let preview = 'Search Query:\n';
|
|
preview += `Keywords: "${keywords}"\n`;
|
|
preview += `Country: ${config.country || 'All'}\n`;
|
|
if(config.city) preview += `City: ${config.city}\n`;
|
|
if(config.industry) preview += `Industry: ${config.industry}\n`;
|
|
|
|
alert(preview);
|
|
}
|
|
|
|
async function exportResults() {
|
|
const fromDate = document.getElementById('exportFrom').value;
|
|
const toDate = document.getElementById('exportTo').value;
|
|
const country = document.getElementById('exportCountry').value;
|
|
|
|
if(!fromDate || !toDate) {
|
|
alert('Please select date range');
|
|
return;
|
|
}
|
|
|
|
// Get selected columns
|
|
const columns = [];
|
|
if(document.getElementById('col_email').checked) columns.push('email');
|
|
if(document.getElementById('col_name').checked) columns.push('name');
|
|
if(document.getElementById('col_title').checked) columns.push('title');
|
|
if(document.getElementById('col_company').checked) columns.push('company');
|
|
if(document.getElementById('col_location').checked) columns.push('location');
|
|
if(document.getElementById('col_industry').checked) columns.push('industry');
|
|
if(document.getElementById('col_source').checked) columns.push('source');
|
|
if(document.getElementById('col_date').checked) columns.push('date');
|
|
|
|
if(columns.length === 0) {
|
|
alert('Please select at least one column to export');
|
|
return;
|
|
}
|
|
|
|
// Get format
|
|
let format = 'csv';
|
|
if(document.getElementById('format_json').checked) format = 'json';
|
|
else if(document.getElementById('format_txt').checked) format = 'txt';
|
|
else if(document.getElementById('format_xlsx').checked) format = 'xlsx';
|
|
|
|
const params = new URLSearchParams({
|
|
action: 'export',
|
|
from: fromDate,
|
|
to: toDate,
|
|
country: country,
|
|
columns: columns.join(','),
|
|
format: format
|
|
});
|
|
|
|
try {
|
|
const r = await fetch(API + '?' + params.toString());
|
|
if(r.ok) {
|
|
const blob = await r.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `scraped-data-${fromDate}-to-${toDate}.${format}`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
window.URL.revokeObjectURL(url);
|
|
} else {
|
|
const error = await r.text();
|
|
alert(`❌ Export failed: ${error}`);
|
|
}
|
|
} catch(e) {
|
|
alert(`❌ Error: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
function clearForm() {
|
|
document.getElementById('keywords').value = '';
|
|
document.getElementById('industry').selectedIndex = 0;
|
|
document.getElementById('country').selectedIndex = 0;
|
|
document.getElementById('city').value = '';
|
|
document.getElementById('companySize').selectedIndex = 0;
|
|
document.getElementById('revenue').selectedIndex = 0;
|
|
document.getElementById('domains').value = '';
|
|
document.getElementById('maxResults').selectedIndex = 1;
|
|
document.getElementById('speed').selectedIndex = 1;
|
|
|
|
// Check all sources
|
|
const checkboxes = ['linkedin', 'hunter', 'apollo', 'rocketreach', 'zoominfo',
|
|
'crunchbase', 'github', 'angellist', 'clearbit'];
|
|
checkboxes.forEach(id => {
|
|
document.getElementById('source_' + id).checked = id === 'linkedin' || id === 'hunter' || id === 'apollo';
|
|
});
|
|
}
|
|
|
|
function resetUI() {
|
|
document.getElementById('scrapingStatus').style.display = 'none';
|
|
document.getElementById('progressBar').style.width = '0%';
|
|
document.getElementById('progressText').textContent = '0%';
|
|
}
|
|
|
|
function switchTab(tabNum) {
|
|
// Update tabs
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
|
|
|
document.querySelector('.tab:nth-child(' + tabNum + ')').classList.add('active');
|
|
document.getElementById('tab' + tabNum).classList.add('active');
|
|
|
|
// Load data for tab 2
|
|
if(tabNum === 2) {
|
|
loadJobs();
|
|
}
|
|
}
|
|
|
|
// ── Drill-down function ──
|
|
async function drillDown(type) {
|
|
const modal = document.getElementById('drillModal');
|
|
const title = document.getElementById('drillTitle');
|
|
const ct = document.getElementById('drillContent');
|
|
modal.style.display = 'flex';
|
|
ct.innerHTML = '<div style="text-align:center;padding:40px;color:var(--t2)">Loading...</div>';
|
|
try {
|
|
let html = '';
|
|
if(type === 'total') {
|
|
title.innerHTML = '📊 Total Scraped — Contacts Extracted';
|
|
const pg = arguments[1] || 1;
|
|
const r = await fetch(API + '?action=drill_total&page=' + pg);
|
|
const d = (await r.json()).data || {};
|
|
html = '<div class="g2" style="margin-bottom:15px">' +
|
|
'<div class="cd"><h3>Verified</h3><div class="sv" style="color:var(--gn)">' + (d.verified||0).toLocaleString() + '</div></div>' +
|
|
'<div class="cd"><h3>Unverified</h3><div class="sv" style="color:var(--or)">' + (d.unverified||0).toLocaleString() + '</div></div>' +
|
|
'</div>' +
|
|
'<h4 style="margin:10px 0 8px;color:var(--cy)">📧 Extracted Contacts (' + (d.total_contacts||0).toLocaleString() + ' total — page ' + (d.page||1) + '/' + (d.pages||1) + ')</h4>' +
|
|
'<div style="overflow-x:auto"><table><thead><tr>' +
|
|
'<th>Email</th><th>Name</th><th>Phone</th><th>Company</th><th>Location</th><th>Source</th><th>✓</th><th>Date</th>' +
|
|
'</tr></thead><tbody>';
|
|
(d.contacts||[]).forEach(function(c){
|
|
var vText = c.is_verified ? '✅' : '❌';
|
|
var src = (c.source||'').length > 30 ? (c.source||'').substring(0,30)+'...' : (c.source||'-');
|
|
html += '<tr>' +
|
|
'<td style="color:var(--cy);font-family:monospace;font-size:12px">' + (c.email||'-') + '</td>' +
|
|
'<td>' + (c.full_name||'<span style="color:#555;font-size:10px">—</span>') + '</td>' +
|
|
'<td>' + (c.phone||'<span style="color:#555;font-size:10px">—</span>') + '</td>' +
|
|
'<td>' + (c.company||'<span style="color:#555;font-size:10px">—</span>') + '</td>' +
|
|
'<td>' + (c.location||'<span style="color:#555;font-size:10px">—</span>') + '</td>' +
|
|
'<td style="font-size:11px" title="' + (c.source||'') + '">' + src + '</td>' +
|
|
'<td style="text-align:center">' + vText + '</td>' +
|
|
'<td style="font-size:11px;color:var(--t2)">' + (c.extracted||'-') + '</td>' +
|
|
'</tr>';
|
|
});
|
|
html += '</tbody></table></div>';
|
|
if((d.pages||1) > 1) {
|
|
html += '<div style="display:flex;gap:8px;justify-content:center;margin-top:12px">';
|
|
if(d.page > 1) html += '<button class="btn btn-sm" onclick="drillDown(\'total\',' + (d.page-1) + ')">◄ Prev</button>';
|
|
html += '<span style="color:var(--t2);line-height:30px">Page ' + d.page + '/' + d.pages + '</span>';
|
|
if(d.page < d.pages) html += '<button class="btn btn-sm" onclick="drillDown(\'total\',' + (d.page+1) + ')">Next ►</button>';
|
|
html += '</div>';
|
|
}
|
|
html += '<h4 style="margin:12px 0 8px;color:var(--t2)">By Source</h4>' +
|
|
'<table><thead><tr><th>Source</th><th>Count</th><th>Verified %</th></tr></thead><tbody>';
|
|
(d.by_source||[]).forEach(function(s){var p=s.total>0?Math.round(s.verified/s.total*100):0;var co=p>70?'var(--gn)':p>40?'var(--or)':'var(--rd)';html+='<tr><td style="font-size:11px">'+s.source+'</td><td>'+Number(s.total).toLocaleString()+'</td><td><span style="color:'+co+'">'+p+'%</span></td></tr>';});
|
|
html+='</tbody></table>';
|
|
} else if(type === 'active') {
|
|
title.innerHTML = '\u26a1 Active Jobs Details';
|
|
const r = await fetch(API + '?action=jobs&status=running');
|
|
const jobs = (await r.json()).data || [];
|
|
if(!jobs.length) { html='<div style="text-align:center;padding:40px;color:var(--t2)">No active jobs</div>'; }
|
|
else { html='<table><thead><tr><th>ID</th><th>Keywords</th><th>Country</th><th>Found</th><th>Started</th></tr></thead><tbody>';
|
|
jobs.forEach(j=>{html+=`<tr><td>#${j.id}</td><td>${j.keywords||'-'}</td><td>${j.country||'ALL'}</td><td style="color:var(--gn)">${j.results_found||0}</td><td>${j.started_at?new Date(j.started_at).toLocaleString():'-'}</td></tr>`;});
|
|
html+='</tbody></table>'; }
|
|
} else if(type === 'success') {
|
|
title.innerHTML = '\u{1f4c8} Success Rate Analysis';
|
|
const r = await fetch(API + '?action=drill_success');
|
|
const s = (await r.json()).data || {};
|
|
html = `<div class="g3" style="margin-bottom:20px">
|
|
<div class="cd"><h3>Completed</h3><div class="sv" style="color:var(--gn)">${s.completed||0}</div></div>
|
|
<div class="cd"><h3>Failed</h3><div class="sv" style="color:var(--rd)">${s.failed||0}</div></div>
|
|
<div class="cd"><h3>Stopped</h3><div class="sv" style="color:var(--or)">${s.stopped||0}</div></div>
|
|
</div><table><thead><tr><th>Source</th><th>Jobs</th><th>Avg Emails</th><th>Rate</th></tr></thead><tbody>`;
|
|
(s.by_source||[]).forEach(x=>{const c=x.rate>70?'var(--gn)':x.rate>40?'var(--or)':'var(--rd)';html+=`<tr><td>${x.source}</td><td>${x.jobs}</td><td>${x.avg_emails}</td><td style="color:${c}">${x.rate}%</td></tr>`;});
|
|
html+='</tbody></table>';
|
|
} else if(type === 'today') {
|
|
title.innerHTML = '\u{1f4c5} Today Hourly Breakdown';
|
|
const r = await fetch(API + '?action=drill_today');
|
|
const t = (await r.json()).data || {};
|
|
html = `<div class="g2" style="margin-bottom:20px">
|
|
<div class="cd"><h3>Today</h3><div class="sv" style="color:var(--pu)">${(t.total||0).toLocaleString()}</div></div>
|
|
<div class="cd"><h3>Last Hour</h3><div class="sv" style="color:var(--cy)">${(t.last_hour||0).toLocaleString()}</div></div>
|
|
</div><div style="display:flex;align-items:flex-end;gap:3px;height:120px;padding:10px 0">`;
|
|
const hrs=t.hourly||[];const mx=Math.max(...hrs.map(h=>h.count),1);
|
|
hrs.forEach(h=>{const p=Math.round(h.count/mx*100);html+=`<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:2px"><span style="font-size:10px;color:var(--t2)">${h.count}</span><div style="width:100%;background:var(--cy);border-radius:2px 2px 0 0;height:${Math.max(p,2)}%;opacity:${p>0?0.3+p/140:0.15}"></div><span style="font-size:9px;color:var(--t2)">${h.hour}h</span></div>`;});
|
|
html+='</div>';
|
|
}
|
|
ct.innerHTML = html;
|
|
} catch(e) { ct.innerHTML = '<div style="color:var(--rd);padding:20px">Error: '+e.message+'</div>'; }
|
|
}
|
|
|
|
function collectSources() {
|
|
const all = ['linkedin','hunter','apollo','rocketreach','zoominfo','crunchbase','clearbit',
|
|
'lusha','snov','dropcontact','kaspr','phantombuster','salesql',
|
|
'facebook','instagram','twitter','tiktok','telegram','whois','yellowpages','yelp','shopify',
|
|
'github','angellist','producthunt','stackoverflow','reddit','forums','googlemaps','webscrape'];
|
|
return all.filter(id => { const el = document.getElementById('source_' + id); return el && el.checked; });
|
|
}
|
|
|
|
// Initial load
|
|
loadStats();
|
|
try{setInterval(loadStats, 30000);}catch(e){}
|
|
|
|
// Load jobs on tab 2
|
|
try{document.getElementById('filterStatus').addEventListener('change', loadJobs);}catch(e){}
|
|
|
|
// Close modal on outside click
|
|
window.onclick = function(event) {
|
|
if(event.target.classList.contains('modal')) {
|
|
event.target.style.display = 'none';
|
|
}
|
|
}
|
|
function deleteOldData(){if(confirm('Delete old scraping data?'))fetch('/api/advanced-scraping.php?action=cleanup',{method:'POST'}).then(r=>r.json()).then(d=>{alert('Cleaned: '+(d.deleted||0)+' records');location.reload()}).catch(()=>alert('Queued'))}
|
|
function previewExport(){fetch('/api/advanced-scraping.php?action=preview_export').then(r=>r.json()).then(d=>{alert('Export preview: '+(d.records||0)+' records ready')}).catch(()=>alert('Preview queued'))}
|
|
function deleteJob(id){if(confirm('Delete job #'+id+'?'))fetch('/api/advanced-scraping.php?action=delete_job&id='+id,{method:'POST'}).then(()=>location.reload()).catch(()=>{})}
|
|
</script>
|
|
<script src="arsenal-common.js?v1770778169"></script>
|
|
<?php include("/opt/wevads-arsenal/public/universal-drill.html"); ?>
|
|
</body></html>
|