Files
html/crm-pipeline-live.html
2026-04-21 14:55:01 +02:00

248 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr"><head>
<meta charset="UTF-8"><title>CRM Pipeline Live · DELIVERADS réactivé</title>
<style>
body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0a0e27;color:#e4e8f7;margin:0;padding:0;line-height:1.5}
.container{max-width:1400px;margin:0 auto;padding:24px}
h1{color:#6ba3ff;border-bottom:2px solid #1e3a8a;padding-bottom:8px;margin-top:0}
h2{color:#c084fc;margin-top:24px}
.card{background:#141933;border:1px solid #263161;border-radius:8px;padding:16px;margin:12px 0}
.flex{display:flex;gap:16px;flex-wrap:wrap}.flex>div{flex:1;min-width:180px;text-align:center}
.num{font-size:28px;font-weight:bold;color:#6ba3ff}
.lbl{color:#9ca8d3;font-size:12px;text-transform:uppercase}
.badge{padding:2px 8px;border-radius:4px;font-size:11px;font-weight:bold;display:inline-block}
.ok{background:#10b981;color:#fff}.warn{background:#f59e0b;color:#111}.ko{background:#ef4444;color:#fff}.info{background:#3b82f6;color:#fff}
table{width:100%;border-collapse:collapse}th,td{padding:8px;border-bottom:1px solid #263161;text-align:left;font-size:13px}
th{background:#1e2549;color:#9ca8d3;font-size:11px;text-transform:uppercase}
button{background:#1e3a8a;color:#fff;border:0;padding:8px 14px;border-radius:4px;cursor:pointer;font-size:12px;margin:2px}
button:hover{background:#2950a7}button.danger{background:#ef4444}button.primary{background:#10b981}
code{background:#0f1529;padding:2px 6px;border-radius:3px;color:#fbbf24;font-family:Monaco,monospace;font-size:12px}
.guard-item{background:#0f1529;padding:10px;border-radius:4px;margin-bottom:6px;border-left:3px solid #10b981}
a{color:#6ba3ff}
pre{background:#000;color:#0f0;padding:12px;border-radius:4px;font-family:Monaco,monospace;font-size:11px;overflow:auto;max-height:300px}
</style></head>
<body>
<div class="container">
<h1>🔄 CRM Pipeline Live · DELIVERADS réactivé avec GUARDS</h1>
<p>Pipeline DELIVERADS reactivé 17avr 14h23 après 2,5 mois disabled. <strong>Option B</strong> — avec rate-limit, volume cap, dashboard. Doctrine 62 CRM-PIPE-GUARDS.</p>
<h2>📊 État pipeline</h2>
<div class="card">
<div class="flex">
<div>
<div class="num" id="total"></div>
<div class="lbl">send_contacts total</div>
</div>
<div>
<div class="num" id="delta-today"></div>
<div class="lbl">Delta aujourd'hui</div>
</div>
<div>
<div class="num" id="last-run"></div>
<div class="lbl">Dernier run</div>
</div>
<div>
<div class="num" id="rc-ok"></div>
<div class="lbl">Runs OK (24h)</div>
</div>
<div>
<div class="num" id="rc-err"></div>
<div class="lbl">Runs en erreur (24h)</div>
</div>
</div>
</div>
<h2>🛡️ Guards actifs (doctrine 62)</h2>
<div class="card">
<div class="guard-item">🔒 <strong>Volume cap</strong> : si <code>send_contacts &gt; 4,000,000</code> → SKIP run (protège DB)</div>
<div class="guard-item">⏱️ <strong>Rate-limit</strong> : si <code>delta &gt; 10K/run</code> → SKIP (évite spike)</div>
<div class="guard-item">💾 <strong>DB load check</strong> : si processes postgres &gt; 200 → SKIP</div>
<div class="guard-item">🔄 <strong>Autoheal</strong> : 10 runs failed consécutifs → cron auto-disabled + alerte Blade queue</div>
<div class="guard-item">📦 <strong>Checkpoint</strong> : <code>/var/lib/deliverads/last_insert_count</code> tracké</div>
</div>
<h2>📅 Fréquences réduites</h2>
<div class="card">
<table>
<tr><th>Task</th><th>Avant (27 jan → 17 avr disabled)</th><th>Nouveau (depuis 17 avr 14h23)</th></tr>
<tr><td>sync_all</td><td>every 2 min (720×/j)</td><td><span class="badge ok">every 30 min (48×/j)</span></td></tr>
<tr><td>ai_engine</td><td>every 5 min (288×/j)</td><td><span class="badge ok">every 1h (24×/j)</span></td></tr>
<tr><td>pipeline</td><td>every 15 min (96×/j)</td><td><span class="badge ok">4×/jour (06/12/18/00)</span></td></tr>
<tr><td>daily_report</td><td>8h</td><td>8h (inchangé)</td></tr>
<tr><td>autoheal check</td><td></td><td><span class="badge info">every 10 min</span></td></tr>
</table>
</div>
<h2>📜 Historique runs (dernières 20)</h2>
<div class="card" id="runs-table">Chargement…</div>
<h2>⚡ Actions</h2>
<div class="card">
<button class="primary" onclick="runNow()">▶️ Run manuel (sync_all)</button>
<button onclick="loadRuns()">🔄 Refresh</button>
<button class="danger" onclick="emergencyStop()">⏸️ STOP emergency (disable cron)</button>
<button onclick="reactivate()">▶️ Reactivate cron</button>
</div>
<h2>🎯 Long terme — Option D migration</h2>
<div class="card">
<p>Option B est un redémarrage conservateur. <strong>Objectif long terme (Option D)</strong> : migrer send_contacts vers pipeline propre :</p>
<ul>
<li><strong>Phase 1 (fait)</strong> : réactivation avec guards (option B) — done 17avr</li>
<li><strong>Phase 2</strong> : observer 7 jours · volumes · erreurs · deliverability</li>
<li><strong>Phase 3</strong> : migrer les sources tracking vers Ethica (pharma HCPs) + Twenty CRM (B2B deals) au lieu de DELIVERADS tracking OVH obsolète</li>
<li><strong>Phase 4</strong> : déprécier DELIVERADS pipeline (passive archive, doctrine 58)</li>
</ul>
<p>Voir <a href="/wiki/DECISION-CRM-SEND-CONTACTS-17AVR.md">décision wiki</a> + <a href="/wiki/P0-BUSINESS-DOSSIERS.md">P0 dossiers</a></p>
</div>
<h2>🔗 Liens</h2>
<div class="card">
<ul>
<li><a href="/api/crm-pipeline-live.php" target="_blank">API crm-pipeline-live JSON</a></li>
<li><a href="/crm-audit.html">CRM audit</a></li>
<li><a href="/database-dashboard-live.html">DB dashboard</a></li>
<li><a href="/tasks-live.html">Tasks & logs live</a></li>
<li><a href="/wevia-master.html">WEVIA Master</a></li>
</ul>
</div>
</div>
<script>
let autoTimer;
async function load() {
try {
const r = await fetch('/api/crm-pipeline-live.php');
const d = await r.json();
if (!d.ok) throw new Error(d.error || 'unk');
document.getElementById('total').textContent = (d.send_contacts_total || 0).toLocaleString();
document.getElementById('delta-today').textContent = (d.delta_today || 0).toLocaleString();
document.getElementById('last-run').textContent = d.last_run_age || '—';
document.getElementById('rc-ok').textContent = d.runs_ok_24h || 0;
document.getElementById('rc-err').textContent = d.runs_err_24h || 0;
const rt = document.getElementById('runs-table');
if (d.runs && d.runs.length) {
rt.innerHTML = '<table><tr><th>TS</th><th>Action</th><th>Delta</th><th>Before</th><th>After</th><th>RC</th></tr>' +
d.runs.map(r => `<tr>
<td style="color:#9ca8d3;font-size:11px">${(r.ts || '').slice(0, 19).replace('T', ' ')}</td>
<td>${r.action}</td>
<td><strong>${r.delta > 0 ? '+' + r.delta : r.delta}</strong></td>
<td style="color:#9ca8d3">${Number(r.count_before).toLocaleString()}</td>
<td>${Number(r.count_after).toLocaleString()}</td>
<td><span class="badge ${r.rc === 0 ? 'ok' : 'ko'}">${r.rc}</span></td>
</tr>`).join('') + '</table>';
} else {
rt.innerHTML = '<p style="color:#9ca8d3">Aucun run encore enregistré. Le cron démarre toutes les 30min, patience.</p>';
}
} catch(e) {
document.getElementById('runs-table').innerHTML = 'Erreur: ' + e.message;
}
}
async function runNow() {
if (!confirm('Lancer un sync_all manuel maintenant ?')) return;
try {
const r = await fetch('/api/crm-pipeline-live.php?action=run_now');
const d = await r.json();
alert('Run result:\n' + JSON.stringify(d).slice(0, 500));
load();
} catch(e) { alert('Error: ' + e.message); }
}
async function emergencyStop() {
if (!confirm('⚠️ DESACTIVER le cron DELIVERADS ?\n\nCa arrête tous les runs. Réversible via bouton "Reactivate".')) return;
try {
const r = await fetch('/api/crm-pipeline-live.php?action=disable');
const d = await r.json();
alert('Disable: ' + JSON.stringify(d));
} catch(e) { alert('Error: ' + e.message); }
}
async function reactivate() {
try {
const r = await fetch('/api/crm-pipeline-live.php?action=enable');
const d = await r.json();
alert('Enable: ' + JSON.stringify(d));
} catch(e) { alert('Error: ' + e.message); }
}
load();
setInterval(load, 30000); // auto-refresh 30s
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
// Clone card content + show close btn + increase font-size
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
// Initial + mutation observer
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
<script src="/api/archi-meta-badge.js" defer></script>
<script src="/api/a11y-auto-enhancer.js" defer></script>
<!-- WTP_UDOCK_V1 (Opus 21-avr t34final) --><script src="/wtp-unified-dock.js" defer></script>
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
</body></html>