fix(web-ia-health w333): regression w331 layout tasks - clean injection + patch backend

CAUSE RACINE (Yacine: tasks vides bas gauche pas centrer):
- Wave 331 avait injecte un JS poller qui creait sa propre liste tasks
- Cette liste etait appendChild au parent du titre Recent Tasks
- MAIS le layout natif est grid 12 cols avec card col-6
- = Liste injectee tombait en colonne gauche bas hors layout

FIX wave 333 (2 patches PROPRES):

1. web-ia-health.html:
   - REMOVE script w331-tasks-poller (pollution UI)
   - SWITCH /api/web-ia-health-cached.php -> /api/web-ia-health.php
     (cached etait band-aid temporaire, plus besoin)

2. /api/web-ia-health.php:
   - PATCH backend pour ALSO parser /tmp/wevia-job-*.log
   - Detecte status: done/failed/pending depuis content
   - Extract label intelligent (Prompt: ou ===)
   - Ajoute au tableau recent_tasks NATIF
   - Le rendu natif card col-6 + feed-item est PROPRE
   - Tasks safficheront dans la JOLIE section au bon endroit

Result attendu apres CF purge + F5:
- Recent Tasks (10) section affiche jusqu a 10 tasks reelles
- Layout natif respecte (card col-6 sur grid 12 cols)
- Statut couleurs: done vert / failed rouge / pending orange
- Plus de pollution gauche bas
- Plus de Aucune task recente

Zero regression (clean restore + additif backend uniquement)
chattr +i toggle, GOLD backup x2 (html + php)
CF purge 2 URLs

Doctrine 333: fix propre via backend natif au lieu de polluer UI
This commit is contained in:
Opus
2026-04-24 21:36:51 +02:00
parent d56acb99f3
commit 576ab22a9f
2 changed files with 29 additions and 96 deletions

View File

@@ -59,7 +59,34 @@ usort($recent_tasks, fn($a,$b)=>$a["age_s"]-$b["age_s"]);
$recent_tasks = array_slice($recent_tasks, 0, 10);
$out["sections"]["tasks"] = $stats;
$out["sections"]["tasks_timeline_24h"] = $buckets;
// === W333: ALSO add /tmp/wevia-job-*.log to recent_tasks ===
$wevia_jobs = glob("/tmp/wevia-job-*.log");
usort($wevia_jobs, fn($a,$b) => filemtime($b) - filemtime($a));
$wevia_jobs = array_slice($wevia_jobs, 0, 10);
foreach ($wevia_jobs as $jf) {
$jname = basename($jf, ".log");
$jmtime = filemtime($jf);
$jcontent = @file_get_contents($jf);
$jstatus = "done";
if (strpos($jcontent, "ERROR") !== false || strpos($jcontent, "FAIL") !== false || strpos($jcontent, "Permission denied") !== false) $jstatus = "failed";
elseif (strpos($jcontent, "elapsed=") === false && strpos($jcontent, "DONE") === false) $jstatus = "pending";
$jlabel = "wevia-gen";
if (preg_match("/Prompt:\s*(.+)/", $jcontent, $m)) $jlabel = "wevia: " . substr(trim($m[1]), 0, 40);
elseif (preg_match("/===\s*(.+?)\s*===/", $jcontent, $m)) $jlabel = trim($m[1]);
$recent_tasks[] = [
"id" => $jname,
"status" => $jstatus,
"label" => $jlabel,
"cmd" => substr($jcontent, 0, 60),
"age_s" => $now - $jmtime,
"created" => date("c", $jmtime)
];
}
usort($recent_tasks, fn($a,$b)=>$a["age_s"]-$b["age_s"]);
$recent_tasks = array_slice($recent_tasks, 0, 10);
$out["sections"]["tasks_recent"] = $recent_tasks;
// === END W333 ===
// === SECTION 3: CDP LOCAL ===
$cdp = [];

View File

@@ -141,7 +141,7 @@ function toast(msg, err){
async function load(){
GRID.classList.add("loading");
try{
const r = await fetch("/api/web-ia-health-cached.php?_="+Date.now(),{cache:"no-store"});
const r = await fetch("/api/web-ia-health.php?_="+Date.now(),{cache:"no-store"});
const d = await r.json();
render(d);
}catch(e){
@@ -463,100 +463,6 @@ function doAsk(){
load();
setInterval(load, 30000);
</script>
<script id="w331-tasks-poller">
(function(){
// W331: Fetch real tasks from /api/tasks-feed.php every 15s
// Also use cached health endpoint (web-ia-health-cached.php) for non-blocking UI
async function fetchTasksFeed(){
try {
const ctl = new AbortController();
const t = setTimeout(() => ctl.abort(), 5000);
const r = await fetch('/api/tasks-feed.php?_=' + Date.now(), {cache:'no-store', signal:ctl.signal});
clearTimeout(t);
if (!r.ok) return;
const d = await r.json();
// Update header counters Tasks
const tasksHeader = document.querySelector('[class*="tasks"]') || document.querySelector('#tasks-counter');
// Try to find "Tasks: 0 done · 0 stale" in the topbar
const topbar = document.body.innerHTML;
// Update Recent Tasks section
const recentTitle = Array.from(document.querySelectorAll('h2,h3,div')).find(el =>
el.textContent && el.textContent.indexOf('Recent Tasks') >= 0 && el.children.length < 3);
if (recentTitle && d.tasks && d.tasks.length > 0) {
let container = recentTitle.parentElement;
// Find or create tasks list
let list = container.querySelector('.w331-tasks-list');
if (!list) {
list = document.createElement('div');
list.className = 'w331-tasks-list';
list.style.cssText = 'margin-top:14px;display:flex;flex-direction:column;gap:8px';
// Remove "Aucune task récente"
const noTasks = Array.from(container.querySelectorAll('div')).find(el =>
el.textContent && el.textContent.trim() === 'Aucune task récente');
if (noTasks) noTasks.remove();
container.appendChild(list);
}
list.innerHTML = d.tasks.slice(0, 10).map(t => {
const colorMap = {done:'#10b981', failed:'#ef4444', pending:'#f59e0b'};
const color = colorMap[t.status] || '#94a3b8';
return `<div style="padding:10px;background:rgba(15,20,30,.5);border-left:3px solid ${color};border-radius:6px;font-size:12px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
<span style="color:${color};font-weight:700;text-transform:uppercase;font-size:10px">● ${t.status}</span>
<span style="color:#64748b;font-size:11px">${t.age_human}</span>
</div>
<div style="color:#e6edf3;font-family:'JetBrains Mono',monospace;font-size:11px;margin-bottom:4px">${t.title}</div>
<div style="color:#64748b;font-size:10px">${t.id}</div>
</div>`;
}).join('');
}
// Update Tasks 24h chart if Chart.js exists
if (typeof Chart !== 'undefined' && d.timeline_24h) {
const canvas = Array.from(document.querySelectorAll('canvas')).find(c =>
c.parentElement && c.parentElement.textContent.indexOf('Tasks 24h') >= 0);
if (canvas) {
const existing = Chart.getChart(canvas);
if (existing) existing.destroy();
new Chart(canvas, {
type: 'bar',
data: {
labels: d.timeline_24h.map(t => t.hour + 'h'),
datasets: [
{label:'Done', data: d.timeline_24h.map(t => t.done), backgroundColor:'#10b981', stack:'s'},
{label:'Failed', data: d.timeline_24h.map(t => t.failed), backgroundColor:'#ef4444', stack:'s'},
{label:'Pending', data: d.timeline_24h.map(t => t.pending), backgroundColor:'#f59e0b', stack:'s'}
]
},
options: {responsive:true, maintainAspectRatio:false, scales:{x:{stacked:true}, y:{stacked:true, beginAtZero:true}}, plugins:{legend:{labels:{color:'#94a3b8'}}}}
});
}
}
// Update topbar counter "Tasks: X done · Y stale"
const taskBadges = document.querySelectorAll('[class*="badge"], .topbar-item, .header-item');
document.body.innerHTML = document.body.innerHTML.replace(
/Tasks:\s*\d+\s*done\s*·\s*\d+\s*stale/g,
`Tasks: ${d.summary.done} done · ${d.summary.failed} failed`
);
} catch(e) {
console.warn('[w331-tasks]', e.message);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function(){
setTimeout(fetchTasksFeed, 1500);
setInterval(fetchTasksFeed, 15000);
});
} else {
setTimeout(fetchTasksFeed, 1500);
setInterval(fetchTasksFeed, 15000);
}
})();
</script>
</body>
</html>