diff --git a/app/views/includes/menu.html b/app/views/includes/menu.html index c90b75fe..06d4be94 100644 --- a/app/views/includes/menu.html +++ b/app/views/includes/menu.html @@ -47,9 +47,9 @@
  • ☁️ Cloud Health
  • ✏️ Brain Config Editor
  • πŸ“‹ Sidebar Admin
  • -
  • πŸš€ Deliverads Dashboard
  • +
  • πŸš€ Deliverads Dashboard
  • 🧠 Brain Admin
  • -
  • 🌱 Seeds Manager
  • +
  • 🌱 Seeds Manager
  • 🌾 Harvest
  • πŸ”₯ Warmup
  • πŸ“€ Send Control
  • diff --git a/public/brain-unified-send.html b/public/brain-unified-send.html index 435c0323..5d6bf577 100644 --- a/public/brain-unified-send.html +++ b/public/brain-unified-send.html @@ -182,8 +182,8 @@ async function loadConfigs() { // Extract unique ISPs const isps = [...new Set(allConfigs.map(c => c.isp_target).filter(Boolean))]; const filterEl = document.getElementById('ispFilters'); - filterEl.innerHTML = '
    ALL
    ' + - isps.map(i => '
    ' + esc(i) + '
    ').join(''); + filterEl.innerHTML = '
    ALL
    ' + + isps.map(i => '
    ' + esc(i) + '
    ').join(''); document.getElementById('configsBadge').textContent = allConfigs.length + ' configs'; document.getElementById('winnersBadge').textContent = allConfigs.filter(c => c.status === 'winner').length + ' winners'; @@ -196,8 +196,9 @@ async function loadConfigs() { function filterISP(isp) { filterIsp = isp; - document.querySelectorAll('#ispFilters .fpill').forEach(p => p.classList.remove('active')); - event.target.classList.add('active'); + document.querySelectorAll('#ispFilters .fpill').forEach(p => { + p.classList.toggle('active', p.dataset.isp === isp); + }); renderWinners(); } @@ -243,7 +244,8 @@ function renderWinners() { function selectConfig(idx, jsonStr) { selectedConfig = JSON.parse(jsonStr); document.querySelectorAll('.winner').forEach(w => w.classList.remove('selected')); - event.currentTarget.classList.add('selected'); + const cards = document.querySelectorAll('.winner'); + if (cards[idx]) cards[idx].classList.add('selected'); document.getElementById('btnSend').disabled = false; if (selectedConfig.from_domain && !document.getElementById('fromEmail').value) { @@ -309,7 +311,6 @@ function esc(s) { if (!s) return ''; const d = document.createElement('div'); d. loadConfigs(); - - diff --git a/public/hamid-history.php b/public/hamid-history.php index b042cc14..328499d1 100644 --- a/public/hamid-history.php +++ b/public/hamid-history.php @@ -1,6 +1,75 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $pdo->exec("CREATE TABLE IF NOT EXISTS hamid_conversations ( + id SERIAL PRIMARY KEY, + session_id VARCHAR(100), + title VARCHAR(255), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() + )"); + + $pdo->exec("CREATE TABLE IF NOT EXISTS hamid_messages ( + id SERIAL PRIMARY KEY, + conversation_id INT, + role VARCHAR(20), + content TEXT, + provider VARCHAR(50), + created_at TIMESTAMP DEFAULT NOW() + )"); + + $action = $_POST['action'] ?? $_GET['action'] ?? ''; + + switch ($action) { + case 'new_conversation': + $stmt = $pdo->prepare("INSERT INTO hamid_conversations (session_id, title) VALUES (?, ?) RETURNING id"); + $stmt->execute([uniqid('conv_'), $_POST['title'] ?? 'Nouvelle conversation']); + echo json_encode(['success' => true, 'id' => $stmt->fetchColumn()]); + break; + + case 'list_conversations': + $convs = $pdo->query("SELECT * FROM hamid_conversations ORDER BY updated_at DESC LIMIT 20")->fetchAll(PDO::FETCH_ASSOC); + echo json_encode(['conversations' => $convs]); + break; + + case 'search': + $q = trim($_GET['q'] ?? $_POST['q'] ?? ''); + $stmt = $pdo->prepare("SELECT * FROM hamid_messages WHERE content ILIKE ? ORDER BY created_at DESC LIMIT 20"); + $stmt->execute(["%{$q}%"]); + echo json_encode(['results' => $stmt->fetchAll(PDO::FETCH_ASSOC)]); + break; + + case 'status': + echo json_encode(['status' => 'online', 'service' => 'HAMID History', 'timestamp' => date('c')]); + break; + + case 'stats': + echo json_encode([ + 'conversations' => (int) $pdo->query("SELECT COUNT(*) FROM hamid_conversations")->fetchColumn(), + 'messages' => (int) $pdo->query("SELECT COUNT(*) FROM hamid_messages")->fetchColumn(), + 'status' => 'online' + ]); + break; + + default: + echo json_encode(['actions' => ['new_conversation', 'list_conversations', 'search', 'stats', 'status']]); + break; + } + } catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); + } + + exit; +} ?> @@ -78,7 +147,7 @@ pre.response{background:#0d1117;border:1px solid #1e293b;border-radius:8px;paddi - - diff --git a/public/send-process.html b/public/send-process.html index 0487f33b..f535d432 100755 --- a/public/send-process.html +++ b/public/send-process.html @@ -168,8 +168,4 @@ setInterval(arsenalLoad, 30000); async function detectISP(email){const r=await fetch('/deliverads/send-engine.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'detect_isp',email:email})});return r.json();} async function sendEmail(payload){const r=await fetch('/deliverads/send-engine.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'send',...payload})});return r.json();} - - - - diff --git a/public/workflow-visual.html b/public/workflow-visual.html index 54d6b30b..a12081dc 100644 --- a/public/workflow-visual.html +++ b/public/workflow-visual.html @@ -72,6 +72,5 @@ try{const r=await fetch(API+'?action=system_health');const d=await r.json();if(d const hist=d.data.executions||[];document.getElementById('tb').innerHTML=hist.slice(0,15).map(e=>`${e.id||'β€”'}${e.workflow||'full_automation'}${e.status||'β€”'}${e.steps_done||0}/${e.steps_total||7}${e.items_processed||0}${e.duration||'β€”'}${e.date||'β€”'}`).join('')}else{renderCanvas({})}}catch(e){renderCanvas({})}} load();setInterval(load,15000); - - diff --git a/public/youtube-factory.html b/public/youtube-factory.html index 91614e24..9d6c5bfa 100644 --- a/public/youtube-factory.html +++ b/public/youtube-factory.html @@ -392,7 +392,6 @@ function loadChannels(){fetch('/api/youtube-factory.php?action=list_channels').t function fetchNewTrends(){loadTrends();var t=document.createElement('div');t.className='toast';t.textContent='Trends refreshed';document.body.appendChild(t);setTimeout(function(){t.remove()},3000)} function runPipeline(){fetch('/api/youtube-factory.php?action=run',{method:'POST'}).then(r=>r.json()).then(d=>{alert('Pipeline: '+(d.status||'started'))}).catch(()=>alert('Pipeline queued'))} - - diff --git a/www/deliverads/brain-dashboard.php b/www/deliverads/brain-dashboard.php index e174f6ea..bfa81605 100644 --- a/www/deliverads/brain-dashboard.php +++ b/www/deliverads/brain-dashboard.php @@ -479,84 +479,58 @@ function previewBody() { } function filterTable() { -function showConfigCategory(cat) { - const configs = allConfigs; - let filtered = []; - let title = ""; - let color = ""; - - if (cat === "winners") { - filtered = configs.filter(c => c.is_winner); - title = "πŸ† Winners - Configs Gagnantes"; - color = "green"; - } else if (cat === "testing") { - filtered = configs.filter(c => !c.is_winner && c.total_sent >= 5); - title = "πŸ”„ Testing - En cours de test"; - color = "yellow"; - } else { - filtered = configs.filter(c => c.total_sent < 5); - title = "πŸ†• New - Nouvelles configs"; - color = "gray"; - } - - let html = `
    -

    ${title} (${filtered.length})

    -
    `; - - if (filtered.length === 0) { - html += '

    Aucune config dans cette catΓ©gorie

    '; - } else { - filtered.forEach(c => { - html += `
    -
    - ${c.isp_target} - ${c.send_method} - ${c.domain_used || ''} -
    -
    - ${Number(c.inbox_rate).toFixed(1)}% - ${c.total_sent} tests -
    -
    `; - }); - } - html += '
    '; - - // Remove previous category detail if exists - const existing = document.getElementById('categoryDetail'); - if (existing) existing.remove(); - - const container = document.createElement('div'); - container.id = 'categoryDetail'; - container.innerHTML = html; - document.querySelector('#configsModal .p-6').appendChild(container); -} const q = document.getElementById('searchConfigs').value.toLowerCase(); - document.querySelectorAll('.config-row').forEach(r => r.style.display = r.textContent.toLowerCase().includes(q) ? '' : 'none'); + document.querySelectorAll('.config-row').forEach(row => { + row.style.display = row.textContent.toLowerCase().includes(q) ? '' : 'none'; + }); } function showConfigCategory(cat) { - const configs = allConfigs; let filtered = []; - let title = ""; - if (cat === "winners") { - filtered = configs.filter(c => c.is_winner); - title = "πŸ† Winners - Configs Gagnantes"; - } else if (cat === "testing") { - filtered = configs.filter(c => !c.is_winner && c.total_sent >= 5); - title = "πŸ”„ Testing - En cours de test"; + let title = ''; + let accentClass = 'text-gray-400'; + + if (cat === 'winners') { + filtered = allConfigs.filter(c => c.is_winner); + title = 'πŸ† Winners - Configs gagnantes'; + accentClass = 'text-green-400'; + } else if (cat === 'testing') { + filtered = allConfigs.filter(c => !c.is_winner && c.total_sent >= 5); + title = 'πŸ”„ Testing - En cours de test'; + accentClass = 'text-yellow-400'; } else { - filtered = configs.filter(c => c.total_sent < 5); - title = "πŸ†• New - Nouvelles configs"; + filtered = allConfigs.filter(c => c.total_sent < 5); + title = 'πŸ†• New - Nouvelles configs'; } - let html = `

    ${title} (${filtered.length})

    `; - filtered.forEach(c => { - html += `
    `; - html += `
    ${c.isp_target} ${c.send_method}
    `; - html += `${Number(c.inbox_rate).toFixed(1)}%
    `; - }); - html += "
    "; - document.querySelector("#configsModal .p-6:last-child").innerHTML += html; + + const wrapper = document.getElementById('configCategoryDetail'); + const titleEl = document.getElementById('categoryTitle'); + const listEl = document.getElementById('categoryList'); + + if (!wrapper || !titleEl || !listEl) return; + + wrapper.classList.remove('hidden'); + titleEl.className = `font-bold text-lg mb-4 ${accentClass}`; + titleEl.textContent = `${title} (${filtered.length})`; + + if (!filtered.length) { + listEl.innerHTML = '
    Aucune config dans cette catΓ©gorie.
    '; + return; + } + + listEl.innerHTML = filtered.map(c => ` +
    +
    +
    ${c.isp_target || 'N/A'}
    +
    ${c.send_method || 'N/A'}${c.domain_used ? ` Β· ${c.domain_used}` : ''}
    +
    +
    +
    ${Number(c.inbox_rate || 0).toFixed(1)}%
    +
    ${c.total_sent || 0} tests
    +
    +
    + `).join(''); } document.querySelectorAll(".modal").forEach(m => m.addEventListener('click', e => { if (e.target === m) closeModal(m.id); })); diff --git a/www/deliverads/index.php b/www/deliverads/index.php index d936bb1e..8ec62990 100644 --- a/www/deliverads/index.php +++ b/www/deliverads/index.php @@ -20,7 +20,7 @@ $s['health'] = round($s['o365'] / max(1, $s['o365'] + 404) * 100); query("SELECT * FROM admin.sponsors WHERE is_active = true ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); // Offers -$offers = []; +try { + $offers = $db->query(" + SELECT + o.*, + s.id AS sponsor_id + FROM admin.offers o + LEFT JOIN admin.sponsors s + ON LOWER(TRIM(s.name)) = LOWER(TRIM(o.sponsor)) + WHERE COALESCE(o.status, 'active') = 'active' + ORDER BY o.name + ")->fetchAll(PDO::FETCH_ASSOC); +} catch (Exception $e) { + $offers = []; +} // Listes disponibles (schemas ISP) $lists = $dbClients->query("SELECT schemaname as schema, tablename as table_name FROM pg_tables WHERE schemaname IN ('gmail', 'hotmail', 'gmx', 'tonline', 'webde', 'videotron', 'yahoo', 'aol')")->fetchAll(PDO::FETCH_ASSOC); // Serveurs disponibles -$servers = $db->query("SELECT id, name, main_ip, status, provider_name FROM admin.mta_servers WHERE status = 'active' ORDER BY name LIMIT 50")->fetchAll(PDO::FETCH_ASSOC); +$servers = $db->query("SELECT id, name, main_ip AS ip_address, status, provider_name FROM admin.mta_servers WHERE status = 'active' ORDER BY name LIMIT 50")->fetchAll(PDO::FETCH_ASSOC); // Sends actifs $activeSends = $db->query("SELECT c.*, s.name as sponsor_name, o.name as offer_name @@ -202,7 +215,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { exit; } - $servers = $db->query("SELECT id, ip_address FROM admin.mta_servers WHERE id IN ($serverIds)")->fetchAll(PDO::FETCH_ASSOC); + $servers = $db->query("SELECT id, COALESCE(ip_address, main_ip) AS ip_address FROM admin.mta_servers WHERE id IN ($serverIds)")->fetchAll(PDO::FETCH_ASSOC); $results = []; $listedCount = 0; @@ -603,8 +616,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { const offerSelect = document.getElementById('offerSelect'); Array.from(offerSelect.options).forEach(opt => { if (opt.value === '') return; - opt.style.display = opt.dataset.sponsor === sponsorId ? '' : 'none'; + opt.style.display = !sponsorId || !opt.dataset.sponsor || opt.dataset.sponsor === sponsorId ? '' : 'none'; }); + offerSelect.value = ''; }); // Get list count diff --git a/www/deliverads/send-engine.php b/www/deliverads/send-engine.php index a8ade085..84cd6698 100644 --- a/www/deliverads/send-engine.php +++ b/www/deliverads/send-engine.php @@ -7,6 +7,18 @@ header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); +// Old dashboards still link directly to this API endpoint. +// Redirect browser navigations to the actual UI instead of showing raw JSON. +if ( + ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET' + && !isset($_GET['action']) + && (strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'text/html') !== false) +) { + header('Content-Type: text/html; charset=utf-8'); + header('Location: /deliverads/send-control.php'); + exit; +} + // DB Connection try { $pdo = new PDO('pgsql:host=localhost;dbname=adx_system', 'admin', 'admin123'); @@ -89,21 +101,27 @@ switch ($action) { break; case 'get_winners': - $isp = $input['isp'] ?? $_GET['isp'] ?? ''; - if (empty($isp)) { - echo json_encode(['error' => 'ISP parameter required']); - exit; + $isp = trim($input['isp'] ?? $_GET['isp'] ?? 'all'); + + if ($isp === '' || strtolower($isp) === 'all') { + $stmt = $pdo->query(" + SELECT * FROM brain_send_configs + WHERE is_winner = true + ORDER BY inbox_rate DESC + "); + $configs = $stmt->fetchAll(PDO::FETCH_ASSOC); + $isp = 'all'; + } else { + $stmt = $pdo->prepare(" + SELECT * FROM brain_send_configs + WHERE isp_target ILIKE :isp + AND is_winner = true + ORDER BY inbox_rate DESC + "); + $stmt->execute(['isp' => "%$isp%"]); + $configs = $stmt->fetchAll(PDO::FETCH_ASSOC); } - $stmt = $pdo->prepare(" - SELECT * FROM brain_send_configs - WHERE isp_target ILIKE :isp - AND is_winner = true - ORDER BY inbox_rate DESC - "); - $stmt->execute(['isp' => "%$isp%"]); - $configs = $stmt->fetchAll(PDO::FETCH_ASSOC); - echo json_encode([ 'isp' => $isp, 'configs' => $configs,