fix(CG6): loadChannels() matcher /notify/channels API-format (OQ-29)

This commit is contained in:
chrischristiansen-glitch 2026-06-15 06:59:56 +02:00
parent bf366f89b0
commit 6367683d81

View File

@ -310,7 +310,6 @@ function renderStats(sum, anomCount){
const cost = sum?.estimated_cost_usd ?? null const cost = sum?.estimated_cost_usd ?? null
const budget = sum?.budget_limit ?? null const budget = sum?.budget_limit ?? null
// Infer tier from cost (approximate GCP spend = cost * 100)
const gcpSpend = cost != null ? cost * 100 : 0 const gcpSpend = cost != null ? cost * 100 : 0
const tier = TIER_MAP.find(t => gcpSpend < t.max) || TIER_MAP[TIER_MAP.length-1] const tier = TIER_MAP.find(t => gcpSpend < t.max) || TIER_MAP[TIER_MAP.length-1]
document.getElementById('tier-badge').textContent = tier.name document.getElementById('tier-badge').textContent = tier.name
@ -394,7 +393,6 @@ function renderModules(data){
<tbody>${rows}</tbody> <tbody>${rows}</tbody>
</table>` </table>`
// Donut
const canvas = document.getElementById('donut-canvas') const canvas = document.getElementById('donut-canvas')
if(donutChart) donutChart.destroy() if(donutChart) donutChart.destroy()
donutChart = new Chart(canvas,{ donutChart = new Chart(canvas,{
@ -418,33 +416,41 @@ function renderModules(data){
} }
// ── DELIVERY CHANNELS ───────────────────────────────────────────────────────── // ── 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(){ async function loadChannels(){
const el = document.getElementById('ch-list') const el = document.getElementById('ch-list')
// Fetch live channel config — falls back to defaults if endpoint not yet wired let apiChannels = []
let channels = null
try{ try{
const r = await fetch('/notify/channels') 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(_){} }catch(_){}
// Sensible defaults with live endpoint awareness if(!apiChannels.length){
const defs = [ el.innerHTML = '<div class="loader" style="color:var(--text-faint)">Kanaler ikke tilgjengelig</div>'
{ icon:'🔗', name:'Webhook', key:'webhook', tier:'Starter+', endpoint:'POST /notify/webhook' }, return
{ 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 = '<div class="ch-list">' + apiChannels.map(ch => {
el.innerHTML = '<div class="ch-list">' + defs.map(ch => { const meta = CH_META[ch.type] || { icon:'📡', name:ch.type, tier:'' }
const cfg = channels?.[ch.key] const active = ch.configured === true
const active = cfg?.active ?? (ch.key==='webhook'||ch.key==='email') // CG5a+CG5b live const detail = ch.url || ch.target || `POST /notify/${ch.type}`
const detail = cfg?.destination || cfg?.url || cfg?.address || ch.endpoint
return `<div class="ch-item"> return `<div class="ch-item">
<div class="ch-icon">${ch.icon}</div> <div class="ch-icon">${meta.icon}</div>
<div class="ch-info"> <div class="ch-info">
<div class="ch-name">${ch.name} <span style="font-size:10px;color:var(--text-faint);font-family:var(--mono);font-weight:400">${ch.tier}</span></div> <div class="ch-name">${meta.name} <span style="font-size:10px;color:var(--text-faint);font-family:var(--mono);font-weight:400">${meta.tier}</span></div>
<div class="ch-detail">${esc(detail)}</div> <div class="ch-detail">${esc(detail)}</div>
</div> </div>
<div class="ch-status ${active?'active':'inactive'}">${active?'Aktiv':'Ikke konfigurert'}</div> <div class="ch-status ${active?'active':'inactive'}">${active?'Aktiv':'Ikke konfigurert'}</div>
${active?`<button class="ch-test-btn" onclick="testChannel('${ch.key}')">Test</button>`:''} ${active?`<button class="ch-test-btn" onclick="testChannel('${ch.type}')">Test</button>`:''}
</div>` </div>`
}).join('') + '</div>' }).join('') + '</div>'
} }
@ -452,7 +458,10 @@ async function loadChannels(){
async function testChannel(key){ async function testChannel(key){
toast('Sender testmelding via '+key+'...') toast('Sender testmelding via '+key+'...')
try{ 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:'<p>Test fra klient-dashboard</p>'})
const r = await fetch('/notify/'+key,{method:'POST',headers:{'Content-Type':'application/json'},body})
if(r.ok) toast('✅ Testmelding sendt via '+key) if(r.ok) toast('✅ Testmelding sendt via '+key)
else toast('⚠️ Svarte '+r.status+' — sjekk konfig') else toast('⚠️ Svarte '+r.status+' — sjekk konfig')
}catch(e){ }catch(e){