266 lines
13 KiB
HTML
266 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,'<');
|
|
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}c·${p.metrics_count||0}m·${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>
|
|
|
|
<script src="/api/a11y-auto-enhancer.js" defer></script>
|
|
<!-- WTP_UDOCK_V1 (Opus 21-avr t31b3) --><script src="/wtp-unified-dock.js" defer></script>
|
|
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
|
|
</body></html>
|