diff --git a/static/costguard-dashboard.html b/static/costguard-dashboard.html index 1e1d60b..4685c69 100644 --- a/static/costguard-dashboard.html +++ b/static/costguard-dashboard.html @@ -310,7 +310,6 @@ function renderStats(sum, anomCount){ const cost = sum?.estimated_cost_usd ?? null const budget = sum?.budget_limit ?? null - // Infer tier from cost (approximate GCP spend = cost * 100) const gcpSpend = cost != null ? cost * 100 : 0 const tier = TIER_MAP.find(t => gcpSpend < t.max) || TIER_MAP[TIER_MAP.length-1] document.getElementById('tier-badge').textContent = tier.name @@ -394,7 +393,6 @@ function renderModules(data){ ${rows} ` - // Donut const canvas = document.getElementById('donut-canvas') if(donutChart) donutChart.destroy() donutChart = new Chart(canvas,{ @@ -418,33 +416,41 @@ function renderModules(data){ } // ── DELIVERY CHANNELS ───────────────────────────────────────────────────────── +// Matcher /notify/channels respons: { channels: [{type, configured, target, url}] } +const CH_META = { + webhook: { icon:'🔗', name:'Webhook', tier:'Starter+' }, + email: { icon:'📧', name:'E-post', tier:'Starter+' }, + sms: { icon:'📱', name:'SMS', tier:'Guard+' }, +} + async function loadChannels(){ const el = document.getElementById('ch-list') - // Fetch live channel config — falls back to defaults if endpoint not yet wired - let channels = null + let apiChannels = [] try{ const r = await fetch('/notify/channels') - if(r.ok) channels = await r.json() + if(r.ok){ + const data = await r.json() + apiChannels = data.channels || [] + } }catch(_){} - // Sensible defaults with live endpoint awareness - const defs = [ - { icon:'🔗', name:'Webhook', key:'webhook', tier:'Starter+', endpoint:'POST /notify/webhook' }, - { icon:'📧', name:'E-post', key:'email', tier:'Starter+', endpoint:'POST /notify/email' }, - { icon:'📱', name:'SMS', key:'sms', tier:'Guard+', endpoint:'POST /notify/sms' }, - ] - el.innerHTML = '
' + defs.map(ch => { - const cfg = channels?.[ch.key] - const active = cfg?.active ?? (ch.key==='webhook'||ch.key==='email') // CG5a+CG5b live - const detail = cfg?.destination || cfg?.url || cfg?.address || ch.endpoint + if(!apiChannels.length){ + el.innerHTML = '
Kanaler ikke tilgjengelig
' + return + } + + el.innerHTML = '
' + apiChannels.map(ch => { + const meta = CH_META[ch.type] || { icon:'📡', name:ch.type, tier:'' } + const active = ch.configured === true + const detail = ch.url || ch.target || `POST /notify/${ch.type}` return `
-
${ch.icon}
+
${meta.icon}
-
${ch.name} ${ch.tier}
+
${meta.name} ${meta.tier}
${esc(detail)}
${active?'Aktiv':'Ikke konfigurert'}
- ${active?``:''} + ${active?``:''}
` }).join('') + '
' } @@ -452,7 +458,10 @@ async function loadChannels(){ async function testChannel(key){ toast('Sender testmelding via '+key+'...') try{ - const r = await fetch('/notify/'+key,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({test:true,message:'CostGuard testmelding fra klient-dashboard'})}) + const body = key==='webhook' + ? JSON.stringify({url:'', event:'custom', title:'CostGuard testmelding', body:'Test fra klient-dashboard'}) + : JSON.stringify({event:'custom', subject:'CostGuard testmelding', body_html:'

Test fra klient-dashboard

'}) + const r = await fetch('/notify/'+key,{method:'POST',headers:{'Content-Type':'application/json'},body}) if(r.ok) toast('✅ Testmelding sendt via '+key) else toast('⚠️ Svarte '+r.status+' — sjekk konfig') }catch(e){