/** * trackiq-api.js v5 * home.html est autonome — ce fichier gère dashboard et logs */ const PAGE=(()=>{ const p=location.pathname; if(p==='/'||p.includes('index')) return 'index'; if(p.includes('home')) return 'home'; if(p.includes('dashboard')) return 'dashboard'; if(p.includes('logs')) return 'logs'; return 'unknown'; })(); /* ══ DASHBOARD ══ */ if(PAGE==='dashboard' && !window.__TRACKIQ_INLINE_DASHBOARD__){ window.addEventListener('load',()=>{ loadDashboard(); setInterval(loadDashboard, 5000); // toutes les 5s, pas 6s }); async function loadDashboard(){ try{ // ── Charge stats backend ── const res = await fetch('/api/dashboard/stats'); const data = await res.json(); const uc = data.global_unique_counts || {}; const scenes = data.scenes || []; const total = Object.values(uc).reduce((a,b)=>a+b,0); // ── Metric card 1 : total objets ── const m1 = document.getElementById('mv1'); if(m1) m1.textContent = total || '—'; // ── Metric card 2 : nb scènes ── const m2 = document.getElementById('mv2'); if(m2) m2.textContent = scenes.length || '—'; // ── Metric card 3 : FPS de la dernière analyse ── const lastScene = scenes[0]; const m3 = document.getElementById('mv3'); if(m3) m3.textContent = lastScene?.fps ? lastScene.fps.toFixed(1) : '—'; // ── Metric card 4 : durée dernière analyse ── const m4 = document.getElementById('mv4'); if(m4) m4.textContent = lastScene?.duration_s ? lastScene.duration_s+'s' : '—'; // ── Scene summary (se met à jour maintenant !) ── if(lastScene){ const ls = document.getElementById('liveSource'); const lsm = document.getElementById('liveSourceMeta'); if(ls) ls.textContent = lastScene.video_name || lastScene.scene_id || 'Unknown'; if(lsm) lsm.textContent = `${lastScene.duration_s||0}s analyzed`; const lo = document.getElementById('liveObjects'); if(lo) lo.textContent = lastScene.total || 0; const lf = document.getElementById('liveFps'); if(lf) lf.textContent = lastScene.fps ? lastScene.fps.toFixed(1) : '—'; const ld = document.getElementById('liveDuration'); if(ld) ld.textContent = lastScene.duration_s ? lastScene.duration_s+'s' : '—'; } // ── Pie chart Object Type Distribution ── if(Object.keys(uc).length && typeof updatePie === 'function'){ updatePie(uc); } // ── Live stats (depuis webcam stats si dispo, sinon dernière analyse) ── try{ const wcRes = await fetch('/api/webcam/stats'); if(wcRes.ok){ const wc = await wcRes.json(); const det = document.getElementById('sbdet'); const acc = document.getElementById('sbacc'); if(det) det.textContent = wc.detections ?? 0; if(acc) acc.textContent = wc.frame ? Math.round(wc.frame) : '—'; // Montre le panneau live seulement si webcam active if(wc.frame > 0){ const note = document.getElementById('livePanelNote'); if(note) note.style.display='none'; } } }catch(e){} // ── Charts flow (vraies données timeline) ── if(window.fch && lastScene?.timeline?.length > 1){ const tl = lastScene.timeline; const step = Math.max(1,Math.floor(tl.length/20)); const lbl=[],objDat=[]; for(let i=0;i!['frame','ts'].includes(k)) .reduce((a,[,v])=>a+(v||0),0); objDat.push(cnt); } window.fch.data.labels = lbl; window.fch.data.datasets[0].data = objDat; // FPS dataset : valeur constante (fps de la scène) window.fch.data.datasets[1].data = lbl.map(()=>lastScene.fps||0); window.fch.update('none'); } // ── Chart classification (vraies classes) ── if(window.tch && Object.keys(uc).length){ const COLORS=['#3b82f6','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899','#06b6d4','#f97316']; window.tch.data.labels = Object.keys(uc); window.tch.data.datasets[0].data = Object.values(uc); window.tch.data.datasets[0].backgroundColor = Object.keys(uc).map((_,i)=>COLORS[i%COLORS.length]); window.tch.update('none'); } // ── Heatmap depuis vraies positions (déjà gérée par loadHeatmap) ── }catch(err){ console.warn('[TrackIQ] Dashboard fetch:',err); } } } /* ══ LOGS ══ */ if(PAGE==='logs'){ let _rows=[]; window.addEventListener('load',async()=>{ try{ const res=await fetch('/api/logs'); const scenes=await res.json(); if(!scenes.length)return; _rows=[]; scenes.forEach(s=>{ Object.entries(s.unique_counts||{}).forEach(([cls,cnt])=>{ _rows.push({date:s.generated_at?new Date(s.generated_at).toLocaleString():'—', scene:s.scene_id,video:s.video_name||'—',cls,cnt,total:s.total_unique||0,sid:s.scene_id}); }); }); const scvs=document.querySelectorAll('.sc-v'); if(scvs[0]) scvs[0].textContent=scenes.reduce((a,s)=>a+(s.total_unique||0),0); if(scvs[1]) scvs[1].textContent=scenes.length; renderTable(_rows); hookFilters(scenes); const expBtn=document.querySelector('.btn-exp'); if(expBtn) expBtn.onclick=()=>scenes.forEach((s,i)=>setTimeout(()=>window.open(`/api/logs/${s.scene_id}/csv`),i*300)); }catch(err){console.warn('[TrackIQ] Logs:',err);} }); function renderTable(rows){ const tbody=document.getElementById('tbody');if(!tbody)return; if(!rows.length){tbody.innerHTML=`No data yet — run an analysis first`;return;} const DOT={'Vehicle':'#3b82f6','Motorcycle':'#10b981','Truck':'#f59e0b','Bus':'#8b5cf6','Human':'#ec4899','Person':'#ec4899','Bicycle':'#06b6d4'}; const EMO={'Vehicle':'🚗','Motorcycle':'🏍','Truck':'🚚','Bus':'🚌','Human':'🚶','Person':'🚶','Bicycle':'🚲'}; tbody.innerHTML=''; rows.forEach(r=>{ const tr=document.createElement('tr'); tr.innerHTML=`
${EMO[r.cls]||'📦'}
${r.date} ${r.scene} ${r.video.length>18?r.video.slice(0,18)+'…':r.video} ${r.cls} ${r.cnt} CSV`; tbody.appendChild(tr); }); } function hookFilters(scenes){ const sIn=document.getElementById('sIn'); const camF=document.getElementById('camF'); const typeF=document.getElementById('typeF'); if(typeF){ const cls=[...new Set(_rows.map(r=>r.cls))]; typeF.innerHTML=''+cls.map(c=>``).join(''); } if(camF) camF.innerHTML=''+ scenes.map(s=>``).join(''); const apply=()=>{ const s=(sIn?.value||'').toLowerCase(),sc=camF?.value||'',tp=typeF?.value||''; renderTable(_rows.filter(r=>(!s||r.video.toLowerCase().includes(s)||r.scene.toLowerCase().includes(s))&&(!sc||r.scene===sc)&&(!tp||r.cls===tp))); }; if(sIn) sIn.oninput=apply; if(camF) camF.onchange=apply; if(typeF) typeF.onchange=apply; } } console.log(`[TrackIQ v5] page=${PAGE}`);