fix: normalize cost field (daily_cost ?? total_cost ?? cost) in getVisibleData, buildDonut, buildSvc, exportCSV
This commit is contained in:
parent
e61bf798c7
commit
bdbe52a6da
|
|
@ -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='<div class="empty-state"><span class="material-symbols-outlined">list</span>Ingen data</div>';
|
||||
return;
|
||||
}
|
||||
const max=Math.max(...top.map(s=>Math.abs(s.total_cost||0)),0.000001);
|
||||
const max=Math.max(...top.map(s=>Math.abs(s.daily_cost ?? s.total_cost ?? s.cost ?? 0)),0.000001);
|
||||
el.innerHTML=top.map((s,i)=>{
|
||||
const c=s.total_cost||0;
|
||||
const c=s.daily_cost ?? s.total_cost ?? s.cost ?? 0;
|
||||
const pct=(Math.abs(c)/max*100).toFixed(1);
|
||||
return`<div class="svc-row">
|
||||
<div class="svc-dot" style="background:${['#7c5cff','#00d4a8','#ffb454','#ff5c7c','#a78bfa','#5eead4','#c4b5fd'][i%7]}"></div>
|
||||
|
|
@ -802,12 +808,11 @@ function exportCSV() {
|
|||
|
||||
const headers = "Tjeneste,Kostnad (NOK)";
|
||||
const rows = data.summary.map(service => {
|
||||
const cost = (service.total_cost || service.cost || 0).toFixed(2);
|
||||
const cost = (service.daily_cost ?? service.total_cost ?? service.cost ?? 0).toFixed(2);
|
||||
return `"${service.service}",${cost}`;
|
||||
});
|
||||
|
||||
const csvContent = "data:text/csv;charset=utf-8," + [headers, ...rows].join("
|
||||
");
|
||||
const csvContent = "data:text/csv;charset=utf-8," + [headers, ...rows].join("\n");
|
||||
|
||||
const encodedUri = encodeURI(csvContent);
|
||||
const link = document.createElement("a");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user