(() => { 'use strict'; const $=id=>document.getElementById(id); let csrf='',selected=null,latest=null, busy=false; const message=(txt,ok=false,id='dvMessage')=>{let e=$(id);e.textContent=txt;e.classList.toggle('ok',ok);}; async function fetchJson(url,options={}){ const r=await fetch(url,{credentials:'same-origin',cache:'no-store',...options}); const d=await r.json().catch(()=>({})); if(!r.ok)throw Error(d.error || 'Erreur HTTP '+r.status); return d; } async function auth(){ const d=await fetchJson('/api/control/auth');csrf=d.csrf||''; $('dvLoginBox').hidden=!!d.authenticated;$('dvApp').hidden=!d.authenticated; if(d.authenticated)await refresh(); } async function getName(){try{const r=await fetchJson('/api/alfred/identity/name');let n=r.name||'Assistant';$('dvName').textContent=n;$('dvInitial').textContent=Array.from(n)[0]?.toLocaleUpperCase('fr-FR')||'A';document.title='Maintenance · '+n;}catch(_){}} function renderTasks(tasks){$('dvCount').textContent=tasks.length+' propositions';const host=$('dvTasks');host.replaceChildren(); if(!tasks.length){let p=document.createElement('p');p.className='dv-muted';p.textContent='Aucune proposition pour le moment.';host.append(p);return;} for(const t of tasks){let btn=document.createElement('button');btn.type='button';btn.className='dv-task'+(selected===t.id?' selected':''); let strong=document.createElement('strong');strong.textContent=t.title;let small=document.createElement('small');small.textContent=t.id+' · '+t.status+' · '+t.changed.length+' fichier(s)';btn.append(strong,small);btn.addEventListener('click',()=>select(t.id));host.append(btn);} } async function refresh(){const r=await fetchJson('/api/control/developer/tasks');renderTasks(r.tasks);if(selected)await select(selected,false);} async function select(id,again=true){selected=id;const d=await fetchJson('/api/control/developer/tasks/'+id);latest=d.task; $('dvEmpty').hidden=true;$('dvSelected').hidden=false;$('dvTitle').textContent=latest.title; $('dvState').textContent=latest.status;$('dvMeta').textContent='Proposition '+id+' · '+latest.changed.length+' fichier(s) · '+latest.created_at; $('dvDiff').textContent=latest.diff||'Aucun changement détecté.'; const c=$('dvChecks');c.replaceChildren();for(const t of latest.checks||[]){let div=document.createElement('div');div.className='dv-check'+(t.ok?'':' fail');div.textContent=(t.ok?'✓ ':'✗ ')+t.file+' — '+t.detail;c.append(div);} if(!c.childNodes.length)c.textContent='Contrôles pas encore réalisés.'; $('dvActions').hidden=latest.status!=='pending';$('dvNext').hidden=latest.status!=='approved'; $('dvCommand').textContent='sudo python3 /opt/alfred/app/tools/alfred_deploy.py deploy '+id+' --apply'; if(again)await refresh(); } async function decide(decision){if(!latest||busy)return;const desc=decision==='approve'?'approuver':'refuser'; if(!confirm('Confirmer : '+desc+' la proposition '+latest.id+' ?'))return; busy=true;try{const id=latest.id;const d=await fetchJson('/api/control/developer/tasks/'+id+'/decision',{ method:'POST',headers:{'Content-Type':'application/json','X-Alfred-CSRF':csrf},body:JSON.stringify({decision,diff_sha256:latest.diff_sha256}) });message('Décision enregistrée : '+d.result.status,true);await select(id);}catch(e){message(e.message);}finally{busy=false;} } $('dvLogin').addEventListener('submit',async e=>{e.preventDefault();try{await fetchJson('/api/control/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:$('dvPassword').value})});$('dvPassword').value='';await auth();}catch(err){message(err.message,false,'dvLoginInfo');}}); $('dvRefresh').addEventListener('click',()=>refresh().catch(err=>message(err.message))); $('dvApprove').addEventListener('click',()=>decide('approve')); $('dvReject').addEventListener('click',()=>decide('reject')); getName();auth().catch(e=>{$('dvLoginBox').hidden=false;message(e.message,false,'dvLoginInfo');}); })();