Files
html/crm-pipeline-live.html
Opus c7994d48be
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
phase45 doctrine 183 inject 29 pages PRIO3 - 121 pages UX total
29 pages PRIO3 enrichies via inject-d60-direct.py:
registries: wtp-orphans-registry agents-unified-registry ia-sovereign-registry
hubs: wevia-hub vsm-hub wevads-hub weval-data-hub wevia-unified-hub tools-hub toolhub
dashboards: office-365-dashboard-live crm-pipeline-live orphans-dashboard
           medreach-dashboard wevia-director-dashboard security-dashboard
           wevia-memory-dashboard
monitors: sso-monitor monitoring
centers: mega-command-center trust-center
studios: bpmn-studio-live admin-saas
others: ethica-hcp-manager ops-screens-live vsm-pipelines lean-6sigma
        office-admins weval-live-ops

Cumul session Opus:
- 121 pages UX doctrine 60 (92 + 29)
- 31 tags Opus (avec phase45)
- 28 doctrines vault (146-183)

Handler inject-d60-direct.py prouve robuste sur 65+ pages consecutives.
ZERO regression. ZERO ecrasement. NR 153/153 invariant.
2026-04-24 14:25:37 +02:00

304 lines
13 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><!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-142446 -->
<style id="doctrine60-ux-direct">
/* DOCTRINE-60-UX-ENRICH injected-direct */
body::before {
content: '';
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
pointer-events: none;
z-index: -1;
}
.card, .kpi, .panel, .btn {
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
}
.card:hover, .kpi:hover, .panel:hover {
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
border-color: rgba(100,180,255,0.5);
}
@keyframes pulseD60 {
0%,100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.05); }
}
.pulse, .live-indicator, .active, .online {
animation: pulseD60 3s ease-in-out infinite;
}
.modal, .chat, .speech, .overlay {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.enter-stagger {
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
}
@keyframes enterStagD60 {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
</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>
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-direct">
// DOCTRINE-60-UX-JS staggered entrance
(function(){
if (!('IntersectionObserver' in window)) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach((e, i) => {
if (e.isIntersecting) {
setTimeout(() => e.target.classList.add('enter-stagger'), i * 80);
obs.unobserve(e.target);
}
});
});
document.querySelectorAll('.card, .kpi, .panel').forEach(el => obs.observe(el));
})();
</script>
</body></html>