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:
@@ -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 = [];
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user