diff --git a/static/billing-dashboard.html b/static/billing-dashboard.html index 53a268c..7dfaae4 100644 --- a/static/billing-dashboard.html +++ b/static/billing-dashboard.html @@ -487,7 +487,7 @@ async function checkAuth() { document.getElementById('user-avatar').src = userProfile.picture; document.getElementById('user-profile').style.display = 'flex'; - switchProvider(currentProvider); // Set initial state + switchProvider(currentProvider); await fetchBudget(); setInterval(fetchAll, REFRESH_MS); @@ -575,7 +575,7 @@ async function fetchAll(){ const [gcpS, gcpL, gcpA, gcpH, awsS, awsL] = await Promise.all([gcpSR.json(), gcpLR.json(), gcpAR.json(), gcpHR.json(), awsSR.json(), awsLR.json()]); currentData.gcp = { summary: gcpS.summary || [], forecast: gcpL, anomalies: gcpA.anomalies || [], history: gcpH.history || [] }; - currentData.aws = { summary: awsS.summary || [], forecast: awsL, anomalies: [], history: [] }; // AWS history/anomalies not implemented yet + currentData.aws = { summary: awsS.summary || [], forecast: awsL, anomalies: [], history: [] }; document.getElementById('err-banner').classList.remove('show'); dot.style.background='var(--ok)'; @@ -591,6 +591,7 @@ async function fetchAll(){ renderAll(); } + function getVisibleData() { if (currentProvider === 'gcp') { return currentData.gcp; @@ -603,15 +604,20 @@ function getVisibleData() { const combined = { summary: [...(currentData.gcp.summary || []), ...(currentData.aws.summary || [])] .reduce((acc, service) => { + const cost = service.daily_cost ?? service.total_cost ?? service.cost ?? 0; const existing = acc.find(s => s.service === service.service); if (existing) { - existing.total_cost += service.total_cost || service.cost || 0; + existing.total_cost = (existing.total_cost || 0) + cost; } else { - acc.push({ ...service }); + acc.push({ ...service, total_cost: cost }); } return acc; }, []) - .sort((a, b) => (b.total_cost || b.cost) - (a.total_cost || a.cost)), + .sort((a, b) => { + const costA = a.daily_cost ?? a.total_cost ?? a.cost ?? 0; + const costB = b.daily_cost ?? b.total_cost ?? b.cost ?? 0; + return costB - costA; + }), forecast: { month_to_date_cost: (currentData.gcp.forecast?.month_to_date_cost || 0) + (currentData.aws.forecast?.month_to_date_cost || 0), daily_average_last_7_days: (currentData.gcp.forecast?.daily_average_last_7_days || 0) + (currentData.aws.forecast?.daily_average_last_7_days || 0), @@ -619,7 +625,7 @@ function getVisibleData() { remaining_days_in_month: currentData.gcp.forecast?.remaining_days_in_month }, anomalies: [...(currentData.gcp.anomalies || []), ...(currentData.aws.anomalies || [])], - history: currentData.gcp.history // Only show GCP history for now + history: currentData.gcp.history }; return combined; } @@ -642,19 +648,15 @@ function calculateDailyCosts(history) { let dailyCost; const entryDate = new Date(entry.date); - // If it's the first day of the month, the daily cost is just the MTD if (entryDate.getDate() === 1) { dailyCost = currentMtd; } else { - // Find the previous day's MTD const prevDayEntry = history[i-1]; const prevMtd = prevDayEntry ? prevDayEntry.mtd : 0; dailyCost = currentMtd - prevMtd; } - // Ensure we don't have negative costs due to billing adjustments dailyCosts.push(Math.max(0, dailyCost)); - labels.push(entryDate.toLocaleDateString('no-NO', {day: 'numeric', month: 'short'})); } @@ -704,7 +706,7 @@ function buildBar(labels, costs){ } function buildDonut(summary){ - const hasData=summary.some(s=>(s.total_cost||0)>0); + const hasData=summary.some(s=>(s.daily_cost ?? s.total_cost ?? s.cost ?? 0)>0); if(!hasData){ document.getElementById('donut-empty').style.display='flex'; document.getElementById('donut-chart').style.display='none'; @@ -715,22 +717,26 @@ function buildDonut(summary){ document.getElementById('donut-chart').style.display='block'; const ctx=document.getElementById('donut-chart').getContext('2d'); if(donutChart)donutChart.destroy(); - const top=summary.filter(s=>(s.total_cost||0)>0).slice(0,7); - donutChart=new Chart(ctx,{type:'doughnut',data:{labels:top.map(s=>s.service),datasets:[{data:top.map(s=>s.total_cost||0),backgroundColor:['#7c5cff','#00d4a8','#ffb454','#ff5c7c','#a78bfa','#5eead4','#c4b5fd'],borderColor:'#13131c',borderWidth:2,hoverBorderWidth:3}]}, + const top=summary.filter(s=>(s.daily_cost ?? s.total_cost ?? s.cost ?? 0)>0).slice(0,7); + donutChart=new Chart(ctx,{type:'doughnut',data:{labels:top.map(s=>s.service),datasets:[{data:top.map(s=>s.daily_cost ?? s.total_cost ?? s.cost ?? 0),backgroundColor:['#7c5cff','#00d4a8','#ffb454','#ff5c7c','#a78bfa','#5eead4','#c4b5fd'],borderColor:'#13131c',borderWidth:2,hoverBorderWidth:3}]}, options:{responsive:true,maintainAspectRatio:false,cutout:'70%', plugins:{legend:{display:false},tooltip:{backgroundColor:'rgba(22,27,34,.97)',borderColor:'rgba(139,148,158,.15)',borderWidth:1,titleColor:'#8b949e',bodyColor:'#e6edf3',callbacks:{label:c=>' '+fmtNOK(c.raw)}}}}}); } function buildSvc(summary){ const el=document.getElementById('svc-list'); - const top=summary.slice(0,8); + const top=[...summary].sort((a,b)=>{ + const costA=a.daily_cost ?? a.total_cost ?? a.cost ?? 0; + const costB=b.daily_cost ?? b.total_cost ?? b.cost ?? 0; + return costB-costA; + }).slice(0,8); if(!top.length){ el.innerHTML='