TRAFFIC_ROAD_APP / static /trackiq-api.js
cyberai-1
Update upload
5bc1431
Raw
History Blame Contribute Delete
8.08 kB
/**
* 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<tl.length;i+=step){
const pt=tl[i];
lbl.push(pt.ts.toFixed(0)+'s');
const cnt=Object.entries(pt)
.filter(([k])=>!['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=`<tr><td colspan="7" style="text-align:center;padding:30px;color:var(--text3);">No data yet — run an analysis first</td></tr>`;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=`<td><div class="thumb">${EMO[r.cls]||'📦'}</div></td>
<td style="font-size:11px;color:var(--text2);">${r.date}</td>
<td style="font-size:11px;color:var(--text2);">${r.scene}</td>
<td style="font-size:11px;" title="${r.video}">${r.video.length>18?r.video.slice(0,18)+'…':r.video}</td>
<td><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${DOT[r.cls]||'#888'};margin-right:4px;"></span>${r.cls}</td>
<td><strong>${r.cnt}</strong></td>
<td><a href="/api/logs/${r.sid}/csv" style="color:var(--accent);font-size:11px;"><i class="fas fa-download"></i> CSV</a></td>`;
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='<option value="">All</option>'+cls.map(c=>`<option>${c}</option>`).join('');
}
if(camF) camF.innerHTML='<option value="">All</option>'+
scenes.map(s=>`<option value="${s.scene_id}">${s.video_name||s.scene_id}</option>`).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}`);