134 lines
11 KiB
HTML
Executable File
134 lines
11 KiB
HTML
Executable File
<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
<title>Orchestrateur Central — Arsenal</title>
|
|
<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">
|
|
<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;--pk:#f472b6;--bl:#3b82f6;--tx:#e2e8f0;--t2:#94a3b8;--t3:#64748b}
|
|
body{background:var(--bg);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;justify-content:space-between}
|
|
.live{display:flex;align-items:center;gap:8px;font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--gn)}.live::before{content:'';width:7px;height:7px;background:var(--gn);border-radius:50%;animation:p 2s infinite}@keyframes p{0%,100%{opacity:1}50%{opacity:.4}}
|
|
.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)}.badge.pk{background:rgba(244,114,182,.15);color:var(--pk)}
|
|
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)}.btn-or{background:rgba(245,158,11,.15);color:var(--or)}.btn-pu{background:rgba(167,139,250,.15);color:var(--pu)}
|
|
.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:250px;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}
|
|
.wf-pipeline{display:flex;flex-direction:column;gap:0;margin-bottom:24px}
|
|
.wf-step{display:flex;align-items:center;gap:16px;padding:14px 16px;background:var(--s);border:1px solid rgba(30,41,59,.3);border-radius:10px;margin-bottom:2px;transition:all .3s;cursor:pointer}
|
|
.wf-step:hover{border-color:rgba(34,211,238,.2);transform:translateX(4px)}
|
|
.wf-step.active{border-color:var(--cy);background:rgba(34,211,238,.04)}
|
|
.wf-step.done{border-color:var(--gn);background:rgba(16,185,129,.04)}
|
|
.wf-step.error{border-color:var(--rd);background:rgba(239,68,68,.04)}
|
|
.wf-num{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;font-family:'JetBrains Mono',monospace;flex-shrink:0}
|
|
.wf-step.active .wf-num{background:rgba(34,211,238,.2);color:var(--cy)}
|
|
.wf-step.done .wf-num{background:rgba(16,185,129,.2);color:var(--gn)}
|
|
.wf-step.idle .wf-num{background:rgba(30,41,59,.5);color:var(--t3)}
|
|
.wf-step.error .wf-num{background:rgba(239,68,68,.2);color:var(--rd)}
|
|
.wf-info{flex:1}.wf-name{font-weight:600;font-size:14px;margin-bottom:2px}.wf-stats{font-size:11px;color:var(--t2);font-family:'JetBrains Mono',monospace}
|
|
.wf-badge{flex-shrink:0}
|
|
.wf-connector{width:2px;height:8px;background:rgba(30,41,59,.4);margin-left:30px}
|
|
.mod-card{background:var(--bg);border:1px solid rgba(30,41,59,.3);border-radius:10px;padding:16px;transition:all .3s;cursor:pointer}
|
|
.mod-card:hover{border-color:rgba(34,211,238,.2);transform:translateY(-2px)}
|
|
.mod-card .mod-icon{font-size:24px;margin-bottom:8px}
|
|
.mod-card .mod-name{font-weight:600;font-size:13px;margin-bottom:4px}
|
|
.mod-card .mod-stat{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--t2)}
|
|
.mod-card .mod-light{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:6px}
|
|
.mod-card .mod-light.on{background:var(--gn);box-shadow:0 0 6px var(--gn)}.mod-card .mod-light.off{background:var(--rd)}
|
|
@media(max-width:1200px){.g4{grid-template-columns:repeat(2,1fr)}}@media(max-width:768px){.g4,.g3,.g2{grid-template-columns:1fr}}
|
|
</style></head><body>
|
|
<div class="hd"><div style="display:flex;align-items:center;gap:10px"><span style="font-size:28px">🎛️</span> Orchestrateur Central</div><div class="live" id="clock">LIVE</div></div>
|
|
<div class="g4">
|
|
<div class="cd"><h3>Workflows Actifs</h3><div class="sv" style="color:var(--cy)" id="k-wf">0</div></div>
|
|
<div class="cd"><h3>Modules Up</h3><div class="sv" style="color:var(--gn)" id="k-mod">0</div></div>
|
|
<div class="cd"><h3>Tâches / Heure</h3><div class="sv" style="color:var(--or)" id="k-tasks">0</div></div>
|
|
<div class="cd"><h3>Auto-Heals Today</h3><div class="sv" style="color:var(--pu)" id="k-heals">0</div></div>
|
|
</div>
|
|
<div class="act">
|
|
<button class="btn btn-gn" onclick="execWF('full_automation')">▶️ Lancer Workflow Complet</button>
|
|
<button class="btn btn-or" onclick="execWF('pause_all')">⏸️ Pause All</button>
|
|
<button class="btn btn-cy" onclick="load()">🔄 Refresh</button>
|
|
<button class="btn btn-pu" onclick="autoHeal()">🩺 Force Auto-Heal</button>
|
|
</div>
|
|
<div class="g2">
|
|
<div class="cd"><h3>⚡ Pipeline Workflow</h3>
|
|
<div class="wf-pipeline" id="wf-pipe"></div>
|
|
</div>
|
|
<div>
|
|
<div class="cd" style="margin-bottom:16px"><h3>📦 Modules Status</h3>
|
|
<div class="g3" id="mod-grid" style="margin-bottom:0"></div>
|
|
</div>
|
|
<div class="cd"><h3>📋 Logs Temps Réel</h3><div class="log" id="log"></div></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
const API='/api/orchestrator.php';
|
|
const WF_STEPS=[
|
|
{num:1,name:'Scraping',icon:'🕷️',api:'/api/advanced-craping.php'},
|
|
{num:2,name:'Personas',icon:'👤',api:'/api/mail-personas.php'},
|
|
{num:3,name:'Temp Email',icon:'📧',api:'/api/temp-email.php'},
|
|
{num:4,name:'CVC Vault',icon:'💳',api:'/api/cvc-vault.php'},
|
|
{num:5,name:'Cloud Account',icon:'☁️',api:'/api/cloud-factory-extended.php'},
|
|
{num:6,name:'Warmup',icon:'🔥',api:'/api/account-creator.php'},
|
|
{num:7,name:'Send',icon:'🚀',api:'/api/send-factory.php'}
|
|
];
|
|
const MODULES=[
|
|
{name:'Scraping Factory',icon:'🕷️',api:'/api/advanced-craping.php'},
|
|
{name:'Mail Personas',icon:'👤',api:'/api/mail-personas.php'},
|
|
{name:'Temp Email',icon:'📧',api:'/api/temp-email.php'},
|
|
{name:'CVC Vault',icon:'💳',api:'/api/cvc-vault.php'},
|
|
{name:'Cloud Factory',icon:'☁️',api:'/api/cloud-factory-extended.php'},
|
|
{name:'Send Factory',icon:'🚀',api:'/api/send-factory.php'},
|
|
{name:'Brain Engine',icon:'🧠',api:'/api/brain-combo.php'},
|
|
{name:'Tracking',icon:'📊',api:'/api/health-check.php'},
|
|
{name:'N8N',icon:'🔄',api:'/api/n8n-orchestrator.php'}
|
|
];
|
|
let logs=[];
|
|
function addLog(msg,type='info'){const t=new Date().toLocaleTimeString('fr-FR');const colors={info:'var(--cy)',success:'var(--gn)',error:'var(--rd)',warn:'var(--or)'};logs.unshift(`<span style="color:var(--t3)">${t}</span> <span style="color:${colors[type]||colors.info}">${msg}</span>`);if(logs.length>50)logs.pop();document.getElementById('log').innerHTML=logs.join('<br>')}
|
|
function uc(){document.getElementById('clock').innerHTML='LIVE '+new Date().toLocaleTimeString('fr-FR')}setInterval(uc,1000);uc();
|
|
function renderPipeline(data){
|
|
const pipe=document.getElementById('wf-pipe');
|
|
pipe.innerHTML=WF_STEPS.map((s,i)=>{
|
|
const st=data.steps?data.steps[i]||{}:{};
|
|
const status=st.status||'idle';
|
|
const items=st.items||0;const dur=st.duration||'—';
|
|
return`${i>0?'<div class="wf-connector"></div>':''}
|
|
<div class="wf-step ${status}"><div class="wf-num">${s.num}</div>
|
|
<div class="wf-info"><div class="wf-name">${s.icon} ${s.name}</div><div class="wf-stats">${items} items • ${dur}</div>
|
|
<div class="bar"><div style="width:${st.progress||0}%;background:${status==='done'?'var(--gn)':status==='active'?'var(--cy)':status==='error'?'var(--rd)':'var(--t3)'}"></div></div>
|
|
</div><div class="wf-badge"><span class="badge ${status==='done'?'gn':status==='active'?'cy':status==='error'?'rd':'or'}">${status}</span></div></div>`
|
|
}).join('')}
|
|
async function renderModules(){
|
|
const grid=document.getElementById('mod-grid');let up=0;
|
|
const checks=await Promise.all(MODULES.map(async m=>{
|
|
try{const r=await fetch(m.api+'?action=stats',{signal:AbortSignal.timeout(3000)});const d=await r.json();return{...m,ok:d.status==='success',data:d.data||{}}
|
|
}catch(e){return{...m,ok:false,data:{}}}}));
|
|
grid.innerHTML=checks.map(m=>{
|
|
if(m.ok)up++;
|
|
const rate=m.data.total||m.data.active||m.data.today||0;
|
|
return`<div class="mod-card"><div class="mod-icon">${m.icon}</div><div class="mod-name"><span class="mod-light ${m.ok?'on':'off'}"></span>${m.name}</div><div class="mod-stat">${rate} items</div></div>`
|
|
}).join('');
|
|
document.getElementById('k-mod').textContent=up;
|
|
return up}
|
|
async function execWF(wf){
|
|
addLog(`Workflow "${wf}" lancé...`,'warn');
|
|
try{const r=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'execute_workflow',workflow:wf})});const d=await r.json();addLog(`Workflow: ${d.status}`,'success')}catch(e){addLog('Erreur workflow: '+e.message,'error')}}
|
|
async function autoHeal(){
|
|
addLog('Auto-heal déclenché...','warn');
|
|
try{const r=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'auto_heal'})});const d=await r.json();addLog(`Auto-heal: ${d.status}`,'success')}catch(e){addLog('Erreur auto-heal: '+e.message,'error')}}
|
|
async function load(){
|
|
addLog('Chargement données...','info');
|
|
try{const r=await fetch(API+'?action=system_health');const d=await r.json();
|
|
if(d.status==='success'&&d.data){
|
|
document.getElementById('k-wf').textContent=d.data.active_workflows||0;
|
|
document.getElementById('k-tasks').textContent=d.data.tasks_per_hour||0;
|
|
document.getElementById('k-heals').textContent=d.data.auto_heals_today||0;
|
|
renderPipeline(d.data);addLog('Données chargées','success')
|
|
}else{renderPipeline({});addLog('API répondu sans data','warn')}
|
|
}catch(e){renderPipeline({});addLog('API orchestrator down: '+e.message,'error')}
|
|
await renderModules()}
|
|
load();setInterval(load,30000);
|
|
</script>
|
|
</body></html>
|