CG3: park AWS billing UI and make history endpoint best-effort so GCP dashboard stays live
This commit is contained in:
parent
88037bdba2
commit
868a0e496d
|
|
@ -204,9 +204,7 @@ footer{padding:10px 20px 16px;font-size:11px;color:var(--faint);text-align:cente
|
||||||
</div>
|
</div>
|
||||||
<div class="tb-right">
|
<div class="tb-right">
|
||||||
<div id="provider-toggle" class="sel" style="display:flex; gap: 4px; padding: 2px;">
|
<div id="provider-toggle" class="sel" style="display:flex; gap: 4px; padding: 2px;">
|
||||||
<button class="sel-opt active" data-provider="gcp" onclick="switchProvider('gcp')">GCP</button>
|
<span class="sel-opt active" data-provider="gcp">GCP</span>
|
||||||
<button class="sel-opt" data-provider="aws" onclick="switchProvider('aws')">AWS</button>
|
|
||||||
<button class="sel-opt" data-provider="all" onclick="switchProvider('all')">Alle</button>
|
|
||||||
</div>
|
</div>
|
||||||
<span id="push-badge" class="badge" style="display:none; background: #333; color: #aaa;">🔔 Varsler aktive</span>
|
<span id="push-badge" class="badge" style="display:none; background: #333; color: #aaa;">🔔 Varsler aktive</span>
|
||||||
<span class="pulse" id="live-dot"></span>
|
<span class="pulse" id="live-dot"></span>
|
||||||
|
|
@ -432,7 +430,7 @@ footer{padding:10px 20px 16px;font-size:11px;color:var(--faint);text-align:cente
|
||||||
const API_BASE = '';
|
const API_BASE = '';
|
||||||
let currentBudget = 500;
|
let currentBudget = 500;
|
||||||
const REFRESH_MS = 5 * 60 * 1000;
|
const REFRESH_MS = 5 * 60 * 1000;
|
||||||
let currentProvider = localStorage.getItem('costguard_provider') || 'gcp';
|
let currentProvider = 'gcp';
|
||||||
|
|
||||||
const DEMO_SUMMARY = [
|
const DEMO_SUMMARY = [
|
||||||
{service:'Cloud Run',total_cost:1.82},{service:'Cloud Build',total_cost:0.61},
|
{service:'Cloud Run',total_cost:1.82},{service:'Cloud Build',total_cost:0.61},
|
||||||
|
|
@ -444,7 +442,7 @@ const DEMO_FORECAST = {month_to_date_cost:3.00,daily_average_last_7_days:0.13,to
|
||||||
const DEMO_ANOMALIES = [];
|
const DEMO_ANOMALIES = [];
|
||||||
|
|
||||||
let barChart,donutChart,period=30;
|
let barChart,donutChart,period=30;
|
||||||
let currentData={gcp: null, aws: null};
|
let currentData={gcp: null};
|
||||||
let userProfile = null;
|
let userProfile = null;
|
||||||
|
|
||||||
function fmtNOK(v,d=2){
|
function fmtNOK(v,d=2){
|
||||||
|
|
@ -469,10 +467,11 @@ async function apiFetch(url, options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchProvider(provider) {
|
function switchProvider(provider) {
|
||||||
currentProvider = provider;
|
// AWS-støtte er parkert; vi holder oss på GCP
|
||||||
localStorage.setItem('costguard_provider', provider);
|
currentProvider = 'gcp';
|
||||||
|
localStorage.setItem('costguard_provider', 'gcp');
|
||||||
document.querySelectorAll('#provider-toggle .sel-opt').forEach(btn => {
|
document.querySelectorAll('#provider-toggle .sel-opt').forEach(btn => {
|
||||||
btn.classList.toggle('active', btn.dataset.provider === provider);
|
btn.classList.toggle('active', btn.dataset.provider === 'gcp');
|
||||||
});
|
});
|
||||||
fetchAll();
|
fetchAll();
|
||||||
}
|
}
|
||||||
|
|
@ -562,20 +561,28 @@ async function fetchAll(){
|
||||||
const gcpForecast = apiFetch(API_BASE+'/billing/live');
|
const gcpForecast = apiFetch(API_BASE+'/billing/live');
|
||||||
const gcpAnomalies = apiFetch(API_BASE+'/billing/anomalies');
|
const gcpAnomalies = apiFetch(API_BASE+'/billing/anomalies');
|
||||||
const gcpHistory = apiFetch(API_BASE+'/billing/history');
|
const gcpHistory = apiFetch(API_BASE+'/billing/history');
|
||||||
|
|
||||||
const awsSummary = apiFetch(API_BASE+'/billing/aws/summary');
|
|
||||||
const awsForecast = apiFetch(API_BASE+'/billing/aws/forecast');
|
|
||||||
|
|
||||||
const [gcpSR, gcpLR, gcpAR, gcpHR, awsSR, awsLR] = await Promise.all([gcpSummary, gcpForecast, gcpAnomalies, gcpHistory, awsSummary, awsForecast]);
|
const [gcpSR, gcpLR, gcpAR, gcpHR] = await Promise.all([gcpSummary, gcpForecast, gcpAnomalies, gcpHistory]);
|
||||||
|
|
||||||
if (!gcpSR.ok || !gcpLR.ok || !gcpAR.ok || !gcpHR.ok || !awsSR.ok || !awsLR.ok) {
|
// GCP summary/forecast/anomalies er kritiske; history er nice-to-have
|
||||||
throw new Error('HTTP error on one or more endpoints');
|
if (!gcpSR.ok || !gcpLR.ok || !gcpAR.ok) {
|
||||||
|
throw new Error('HTTP error on core billing endpoints');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [gcpS, gcpL, gcpA, gcpH, awsS, awsL] = await Promise.all([gcpSR.json(), gcpLR.json(), gcpAR.json(), gcpHR.json(), awsSR.json(), awsLR.json()]);
|
const [gcpS, gcpL, gcpA] = await Promise.all([gcpSR.json(), gcpLR.json(), gcpAR.json()]);
|
||||||
|
|
||||||
|
let gcpH = { history: [] };
|
||||||
|
if (gcpHR.ok) {
|
||||||
|
try {
|
||||||
|
gcpH = await gcpHR.json();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not parse history JSON:', e.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('History endpoint returned', gcpHR.status);
|
||||||
|
}
|
||||||
|
|
||||||
currentData.gcp = { summary: gcpS.summary || [], forecast: gcpL, anomalies: gcpA.anomalies || [], history: gcpH.history || [] };
|
currentData.gcp = { summary: gcpS.summary || [], forecast: gcpL, anomalies: gcpA.anomalies || [], history: gcpH.history || [] };
|
||||||
currentData.aws = { summary: awsS.summary || [], forecast: awsL, anomalies: [], history: [] };
|
|
||||||
|
|
||||||
document.getElementById('err-banner').classList.remove('show');
|
document.getElementById('err-banner').classList.remove('show');
|
||||||
dot.style.background='var(--ok)';
|
dot.style.background='var(--ok)';
|
||||||
|
|
@ -583,7 +590,6 @@ async function fetchAll(){
|
||||||
console.warn('API feil, demo-data:',err.message);
|
console.warn('API feil, demo-data:',err.message);
|
||||||
if (err.message.includes("Not logged in")) return;
|
if (err.message.includes("Not logged in")) return;
|
||||||
currentData.gcp = { summary: DEMO_SUMMARY, forecast: DEMO_FORECAST, anomalies: DEMO_ANOMALIES, history: [] };
|
currentData.gcp = { summary: DEMO_SUMMARY, forecast: DEMO_FORECAST, anomalies: DEMO_ANOMALIES, history: [] };
|
||||||
currentData.aws = { summary: [], forecast: {}, anomalies: [], history: [] };
|
|
||||||
document.getElementById('err-msg').textContent='API utilgjengelig: '+err.message+' — demo-data vises.';
|
document.getElementById('err-msg').textContent='API utilgjengelig: '+err.message+' — demo-data vises.';
|
||||||
document.getElementById('err-banner').classList.add('show');
|
document.getElementById('err-banner').classList.add('show');
|
||||||
dot.style.background='var(--red)';
|
dot.style.background='var(--red)';
|
||||||
|
|
@ -593,43 +599,8 @@ async function fetchAll(){
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVisibleData() {
|
function getVisibleData() {
|
||||||
if (currentProvider === 'gcp') {
|
// AWS er parkert; all visning baseres på GCP-data
|
||||||
return currentData.gcp;
|
return currentData.gcp;
|
||||||
}
|
|
||||||
if (currentProvider === 'aws') {
|
|
||||||
return currentData.aws;
|
|
||||||
}
|
|
||||||
if (currentProvider === 'all') {
|
|
||||||
if (!currentData.gcp || !currentData.aws) return null;
|
|
||||||
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 = (existing.total_cost || 0) + cost;
|
|
||||||
} else {
|
|
||||||
acc.push({ ...service, total_cost: cost });
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
.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),
|
|
||||||
total_monthly_forecast: (currentData.gcp.forecast?.total_monthly_forecast || 0) + (currentData.aws.forecast?.total_monthly_forecast || 0),
|
|
||||||
remaining_days_in_month: currentData.gcp.forecast?.remaining_days_in_month
|
|
||||||
},
|
|
||||||
anomalies: [...(currentData.gcp.anomalies || []), ...(currentData.aws.anomalies || [])],
|
|
||||||
history: currentData.gcp.history
|
|
||||||
};
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -640,7 +611,6 @@ function calculateDailyCosts(history) {
|
||||||
|
|
||||||
const dailyCosts = [];
|
const dailyCosts = [];
|
||||||
const labels = [];
|
const labels = [];
|
||||||
let lastMtd = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < history.length; i++) {
|
for (let i = 0; i < history.length; i++) {
|
||||||
const entry = history[i];
|
const entry = history[i];
|
||||||
|
|
@ -828,6 +798,7 @@ function exportPDF() {
|
||||||
window.print();
|
window.print();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', checkAuth);
|
document.addEventListener('DOMContentLoaded', checkAuth);
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
|
|
@ -846,7 +817,7 @@ if ('serviceWorker' in navigator) {
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
cursor: pointer;
|
cursor: default;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user