Files
html/linkedin-control-v98.html
2026-04-20 15:30:03 +02:00

262 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="fr"><head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>LinkedIn Full Auto V98 - WEVAL</title>
<style>
:root{--bg:#0a0e1a;--bg2:#141b2d;--bg3:#1e2740;--fg:#e8ecf4;--t2:#9aa5c0;--t3:#5f6b85;--brd:rgba(255,255,255,.08);--gold:#d4af37;--em:#10b981;--cy:#06b6d4;--am:#f59e0b;--co:#ef4444;--vi:#8b5cf6;--sa:#3b82f6;}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--fg);font-family:'SF Pro Display',system-ui,sans-serif;min-height:100vh;padding:20px}
.hdr{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--brd)}
.hdr h1{font-size:24px}.hdr h1 span{color:var(--gold)}
.sub{color:var(--t2);font-size:12px;margin-top:4px}
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;margin-left:6px}
.badge.ok{background:var(--em);color:#000}.badge.err{background:var(--co);color:#fff}.badge.warn{background:var(--am);color:#000}
.btn{padding:8px 14px;background:var(--bg3);border:1px solid var(--brd);border-radius:6px;color:var(--fg);cursor:pointer;font-size:12px;font-weight:500;text-decoration:none;display:inline-block}
.btn:hover{background:var(--gold);color:#000}
.btn.primary{background:var(--gold);color:#000}
.btn.success{background:var(--em);color:#000}.btn.danger{background:var(--co);color:#fff}
.btns{display:flex;gap:8px;flex-wrap:wrap}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-bottom:20px}
.mc{background:var(--bg2);border:1px solid var(--brd);border-left:3px solid var(--gold);padding:12px;border-radius:8px}
.mc-l{font-size:9px;color:var(--t2);text-transform:uppercase;letter-spacing:1px}
.mc-v{font-size:24px;font-weight:700;margin:4px 0}
.mc-s{font-size:10px;color:var(--em)}
.mc.v{border-left-color:var(--vi)}.mc.c{border-left-color:var(--cy)}.mc.e{border-left-color:var(--em)}.mc.a{border-left-color:var(--am)}.mc.r{border-left-color:var(--co)}
.cols{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
@media(max-width:1100px){.cols{grid-template-columns:1fr}}
.col{background:var(--bg2);border:1px solid var(--brd);border-radius:10px;padding:14px;max-height:650px;overflow-y:auto}
.col h2{font-size:12px;font-weight:700;color:var(--t2);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:12px;display:flex;justify-content:space-between}
.col h2 .count{background:var(--bg3);padding:2px 8px;border-radius:10px;color:var(--gold);font-size:10px}
.post{background:var(--bg3);border:1px solid var(--brd);border-radius:8px;padding:11px;margin-bottom:10px}
.post-h{display:flex;justify-content:space-between;font-size:10px;color:var(--t3);margin-bottom:6px}
.post-h .theme{color:var(--cy);font-weight:600;text-transform:uppercase}
.post-body{font-size:11px;line-height:1.5;max-height:160px;overflow-y:auto;background:var(--bg);padding:8px;border-radius:4px;margin-bottom:8px;white-space:pre-wrap}
.post-meta{display:flex;gap:8px;font-size:9px;color:var(--t3);margin-bottom:8px;flex-wrap:wrap}
.post-actions{display:flex;gap:4px;flex-wrap:wrap}
.post-actions button{padding:4px 9px;border:1px solid var(--brd);border-radius:4px;font-size:10px;cursor:pointer;background:var(--bg);color:var(--t2)}
.post-actions button.approve{background:var(--em);color:#fff;border-color:var(--em)}
.post-actions button.auto{background:var(--gold);color:#000;border-color:var(--gold);font-weight:600}
.post-actions button.reject{background:var(--co);color:#fff;border-color:var(--co)}
.spnr{display:inline-block;width:12px;height:12px;border:2px solid var(--gold);border-top:2px solid transparent;border-radius:50%;animation:sp 1s linear infinite;vertical-align:middle}
@keyframes sp{to{transform:rotate(360deg)}}
.empty{text-align:center;color:var(--t3);padding:30px;font-size:12px;font-style:italic}
.session-box{background:var(--bg2);border:1px solid var(--brd);border-radius:10px;padding:16px;margin-bottom:20px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}
.session-status{display:flex;align-items:center;gap:10px;font-size:13px}
.session-status.logged{color:var(--em)}.session-status.expired{color:var(--co)}.session-status.missing{color:var(--am)}
.log-panel{background:var(--bg2);border:1px solid var(--brd);border-radius:8px;padding:12px;margin-top:20px}
.log-panel h2{font-size:11px;color:var(--t2);margin-bottom:8px}
.log-panel pre{font-size:10px;max-height:200px;overflow-y:auto;background:var(--bg);padding:8px;border-radius:4px;white-space:pre-wrap;color:var(--t2)}
</style></head><body>
<div class="hdr">
<div>
<h1>LinkedIn <span>Full Auto V98</span> <span id="sessionBadge" class="badge warn">Session: checking</span></h1>
<div class="sub">Browser-piloted by WEVIA Master · Playwright Chromium persistent · Doctrine #100 · Cron */20min</div>
</div>
<div class="btns">
<a href="/linkedin-automation-v96.html" class="btn">V96 Generator</a>
<a href="/linkedin-control-v97.html" class="btn">V97 Control</a>
<a href="/weval-technology-platform.html" class="btn">WTP</a>
<a href="/wevia-master.html" class="btn primary">WEVIA Chat</a>
</div>
</div>
<div class="session-box">
<div class="session-status" id="sessionStatus">⟳ Checking browser session...</div>
<div class="btns">
<button class="btn" onclick="injectSession()">💉 Inject LI_AT cookie</button>
<button class="btn" onclick="checkSession()">🔍 Check session</button>
<button class="btn success" onclick="triggerBrowserCron()">⚡ Trigger Browser Publish Now</button>
</div>
</div>
<div class="grid" id="kpiGrid"></div>
<div class="btns" style="margin-bottom:20px">
<button class="btn primary" onclick="gen('wevia_sovereign_ai')">🤖 Generate WEVIA</button>
<button class="btn primary" onclick="gen('ethica_hcp')">💊 Ethica</button>
<button class="btn primary" onclick="gen('vistex_sap')">🏢 Vistex</button>
<button class="btn primary" onclick="gen('case_study')">📊 Case</button>
<button class="btn" onclick="loadAll()">🔄 Refresh</button>
</div>
<div class="cols">
<div class="col">
<h2>📝 Drafts <span id="draftsCount" class="count">0</span></h2>
<div id="drafts"></div>
</div>
<div class="col">
<h2>⏰ Scheduled <span id="schedCount" class="count">0</span></h2>
<div id="scheduled"></div>
</div>
<div class="col">
<h2>✅ Published <span id="pubCount" class="count">0</span></h2>
<div id="published"></div>
</div>
</div>
<div class="log-panel">
<h2>V97/V98 Cron Log</h2>
<pre id="log">Loading...</pre>
</div>
<script>
const API='/api/v97-linkedin-control.php';
const V96='/api/v96-linkedin-automation.php';
async function checkSession(){
const status=document.getElementById('sessionStatus');
const badge=document.getElementById('sessionBadge');
status.innerHTML='⟳ Running Playwright check (may take 30-60s)...';
status.className='session-status';
try{
const r=await fetch(API+'?action=browser_session_status');
const s=await r.json();
if(s.session_exists){
status.innerHTML='✅ Session file present · age '+s.age_hours+'h · last '+s.last_update;
status.className='session-status logged';
badge.textContent='Session: exists';badge.className='badge ok';
}else{
status.innerHTML='⚠️ No session cookies · inject LI_AT from /etc/weval/secrets.env (see below)';
status.className='session-status missing';
badge.textContent='Session: missing';badge.className='badge warn';
}
}catch(e){
status.innerHTML='❌ Error: '+e.message;
status.className='session-status expired';
badge.textContent='Session: error';badge.className='badge err';
}
}
async function injectSession(){
const status=document.getElementById('sessionStatus');
status.innerHTML='⟳ Injecting LI_AT cookie from secrets.env...';
const r=await fetch(API+'?action=browser_inject_session');
const d=await r.json();
if(d.ok && d.logged_in){
status.innerHTML='✅ Browser session OK · logged in as Weval';
status.className='session-status logged';
document.getElementById('sessionBadge').textContent='Session: ready';
document.getElementById('sessionBadge').className='badge ok';
}else if(d.err){
status.innerHTML='⚠️ '+d.err+' — '+(d.instruction||'Add LI_AT=... to /etc/weval/secrets.env');
status.className='session-status missing';
}else{
status.innerHTML='❌ '+JSON.stringify(d);
}
checkSession();
}
async function triggerBrowserCron(){
const b=event.target;const o=b.innerHTML;
b.innerHTML='<span class="spnr"></span> Running browser publish (up to 3min)';b.disabled=true;
const r=await fetch(API+'?action=browser_publish_due');
const d=await r.json();
alert('Browser cron: '+JSON.stringify(d,null,2));
b.innerHTML=o;b.disabled=false;
loadAll();
}
async function loadOverview(){
const r=await fetch(API+'?action=overview');const d=await r.json();
const st=d.page_stats||{};
document.getElementById('kpiGrid').innerHTML=[
['Drafts','v',d.queue_drafts,'pending approval'],
['Scheduled','a',d.scheduled,'cron */20min'],
['Published','e',d.published_count,'today: '+d.published_today],
['Followers','s',st.followers||0,'+'+(st.last_7d_new_followers||0)+' 7d'],
['Impressions 7d','e',st.last_7d_post_impressions||0,'+'+(st.pct_growth_impressions||0)+'%'],
['Pixel','c',d.pixel_hits_month||0,'LinkedIn ref'],
].map(([l,c,v,sub])=>`<div class="mc ${c}"><div class="mc-l">${l}</div><div class="mc-v">${v}</div><div class="mc-s">${sub}</div></div>`).join('');
}
async function loadQueues(){
const r=await fetch(API+'?action=all_queues');const d=await r.json();
const drafts=(d.queue_drafts||[]).reverse();
document.getElementById('draftsCount').textContent=drafts.length;
document.getElementById('drafts').innerHTML=drafts.length?drafts.map(p=>renderPost(p,'draft')).join(''):'<div class="empty">No drafts</div>';
const sched=d.scheduled||[];
document.getElementById('schedCount').textContent=sched.length;
document.getElementById('scheduled').innerHTML=sched.length?sched.map(p=>renderPost(p,'scheduled')).join(''):'<div class="empty">Nothing scheduled</div>';
const pub=(d.published||[]).slice().reverse().slice(0,10);
document.getElementById('pubCount').textContent=(d.published||[]).length;
document.getElementById('published').innerHTML=pub.length?pub.map(p=>renderPost(p,'published')).join(''):'<div class="empty">No published yet</div>';
}
function renderPost(p,col){
const body=(p.post||'').replace(/</g,'&lt;');
const status=p.status||'draft';
let actions='';
if(col==='draft'){
actions=`
<button class="auto" onclick="browserPub('${p.id}')">🤖 Publish via Browser</button>
<button class="approve" onclick="act('approve','${p.id}')">✓ Approve</button>
<button onclick="schedulePrompt('${p.id}')">⏰ Schedule</button>
<button class="reject" onclick="act('reject','${p.id}')">🗑</button>
`;
}else if(col==='scheduled'){
actions=`<span style="color:var(--am);font-size:10px">⏰ ${new Date(p.scheduled_at).toLocaleString('fr')} · Auto-publish via browser</span>`;
}else{
actions=`<span style="color:var(--em);font-size:10px">✅ ${new Date(p.published_at).toLocaleString('fr')} via ${p.published_via||'?'}</span>`;
if(p.screenshot){actions+=` · <a href="${p.screenshot.replace('/var/www/html','')}" target="_blank" style="color:var(--cy);font-size:10px">📷 proof</a>`;}
}
return `<div class="post" id="p-${p.id}">
<div class="post-h"><span class="theme">${p.theme||'?'}</span><span>${p.chars||0}${p.metrics_count||0}${p.hashtags_count||0}#</span></div>
<div class="post-body">${body}</div>
<div class="post-actions">${actions}</div>
</div>`;
}
async function browserPub(id){
const b=event.target;const o=b.innerHTML;
b.innerHTML='<span class="spnr"></span> Publishing via browser';b.disabled=true;
const r=await fetch(API+'?action=browser_publish_id&id='+id);
const d=await r.json();
if(d.results && d.results[0] && d.results[0].ok){
alert('✅ Published! Screenshot: '+d.results[0].screenshot_proof);
}else{
alert('❌ '+JSON.stringify(d,null,2));
}
b.innerHTML=o;b.disabled=false;
loadAll();
}
async function act(action,id){
if(action==='reject'&&!confirm('Delete?'))return;
const r=await fetch(API+'?action='+action+'&id='+id);
const d=await r.json();
if(d.ok){loadAll();}else{alert('Failed: '+JSON.stringify(d));}
}
async function schedulePrompt(id){
const hrs=prompt('Schedule in hours from now?','2');
if(!hrs)return;
const when=new Date(Date.now()+parseInt(hrs)*3600*1000).toISOString();
const r=await fetch(API+'?action=schedule&id='+id+'&when='+encodeURIComponent(when));
const d=await r.json();
if(d.ok){loadAll();}else{alert('Failed');}
}
async function gen(theme){
const b=event.target;const o=b.innerHTML;
b.innerHTML='<span class="spnr"></span> Generating';b.disabled=true;
await fetch(V96+'?action=generate_post&theme='+theme);
b.innerHTML=o;b.disabled=false;
loadAll();
}
async function loadLog(){
try{
const r=await fetch(API+'?action=log');const d=await r.json();
document.getElementById('log').textContent=d.log||'(no runs yet)';
}catch(e){}
}
async function loadAll(){await loadOverview();await loadQueues();await loadLog();}
checkSession();
loadAll();
setInterval(loadAll,30000);
</script>
</body></html>