From 24ee2367a2ecc00f030a66ea860c6dd5de65a57c8c1887f6fcb03f032ad1a257 Mon Sep 17 00:00:00 2001 From: larssand Date: Wed, 24 Jun 2026 20:26:37 +0200 Subject: [PATCH] refresh issue --- src/fgai/dashboard.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fgai/dashboard.py b/src/fgai/dashboard.py index 6e46931..9b54f65 100644 --- a/src/fgai/dashboard.py +++ b/src/fgai/dashboard.py @@ -95,6 +95,7 @@ function table(rows, columns) { function capability(label, state, detail) { return `${esc(label)}: ${esc(detail)}`; } function drawTrend(history) { const canvas=document.getElementById('trendChart'), ctx=canvas.getContext('2d'), w=canvas.width=canvas.clientWidth*devicePixelRatio, h=canvas.height=canvas.clientHeight*devicePixelRatio; ctx.scale(devicePixelRatio,devicePixelRatio); const cw=canvas.clientWidth,ch=canvas.clientHeight; ctx.clearRect(0,0,cw,ch); const max=Math.max(1,...history.map(item=>item.events||0)); const line=(key,color)=>{ctx.strokeStyle=color;ctx.lineWidth=2;ctx.beginPath();history.forEach((item,index)=>{const x=12+index*Math.max(1,(cw-24)/Math.max(1,history.length-1));const y=ch-18-((item[key]||0)/max)*(ch-36);index?ctx.lineTo(x,y):ctx.moveTo(x,y)});ctx.stroke()}; line('events','#1ea9ff');line('anomalies','#ff5656'); } async function refresh() { + const openDetails = new Set([...document.querySelectorAll('details[open][data-detail-id]')].map(item => item.dataset.detailId)); const res = await fetch('/api/status', {cache: 'no-store'}); const data = await res.json(); const s = data.summary || {}; @@ -157,7 +158,7 @@ async function refresh() { {label:'Severity', render:r => `${esc(r.severity)}`}, {label:'Streams', render:r => esc((r.correlated_streams || []).join(', ') || 'single stream')}, {label:'Evidence', render:r => esc((r.evidence || []).join('; '))}, - {label:'Timeline', render:r => { const rows=(r.timeline||[]).map(item => esc(`${item.timestamp || ''} | ${item.stream || ''} | ${item.action || ''} | ${item.destination || ''} | ${item.context || item.message || ''}`)).join('
'); return rows ? `
${esc(`${r.first_seen || '-'} to ${r.last_seen || '-'}`)}

${rows}

` : '-'; }} + {label:'Timeline', render:r => { const rows=(r.timeline||[]).map(item => esc(`${item.timestamp || ''} | ${item.stream || ''} | ${item.action || ''} | ${item.destination || ''} | ${item.context || item.message || ''}`)).join('
'); const id=`incident:${r.entity}:${r.first_seen || ''}`; return rows ? `
${esc(`${r.first_seen || '-'} to ${r.last_seen || '-'}`)}

${rows}

` : '-'; }} ]); document.getElementById('blocks').innerHTML = table(data.block_candidates || [], [ {label:'Source', key:'src_ip'}, @@ -169,7 +170,7 @@ async function refresh() { const streamTitles = Object.fromEntries((configuration.graylog_streams || []).map(item => [item.id, item.title || item.id])); const fieldRows = Object.entries(data.field_deviations || {}).flatMap(([entity, deviations]) => (deviations || []).map(item => ({entity, stream_title: streamTitles[item.stream_id] || item.stream_id, ...item}))); document.getElementById('fieldDeviations').innerHTML = table(fieldRows, [ - {label:'Entity', key:'entity'}, {label:'Stream', key:'stream_title'}, {label:'Field', key:'field'}, {label:'Score', key:'score'}, {label:'Review', render:r => esc(r.feedback || 'unreviewed')}, {label:'Evidence', render:r => { const summary=esc(`${r.reason}; current ${r.current ?? '-'} vs baseline ${r.baseline ?? '-'}; values: ${(r.sample_values || []).join(', ') || '-'}`); const events=(r.sample_events || []).map(item => esc(`${item.timestamp} | ${item.source} -> ${item.destination} | ${item.action} ${item.service} | ${item.value} | ${item.message}`)).join('
'); return events ? `
${summary}

${events}

` : summary; }}, {label:'Review action', render:r => `
`} + {label:'Entity', key:'entity'}, {label:'Stream', key:'stream_title'}, {label:'Field', key:'field'}, {label:'Score', key:'score'}, {label:'Review', render:r => esc(r.feedback || 'unreviewed')}, {label:'Evidence', render:r => { const summary=esc(`${r.reason}; current ${r.current ?? '-'} vs baseline ${r.baseline ?? '-'}; values: ${(r.sample_values || []).join(', ') || '-'}`); const events=(r.sample_events || []).map(item => esc(`${item.timestamp} | ${item.source} -> ${item.destination} | ${item.action} ${item.service} | ${item.value} | ${item.message}`)).join('
'); const id=`deviation:${r.entity}:${r.stream_id}:${r.field}:${r.value || ''}`; return events ? `
${summary}

${events}

` : summary; }}, {label:'Review action', render:r => `
`} ]); document.querySelectorAll('.feedback').forEach(button => button.addEventListener('click', async () => { const note = prompt('Review note (optional):') || ''; @@ -215,6 +216,7 @@ async function refresh() { '

Top Source Ports

' + table(d.top_source_ports || [], [{label:'Value', key:'value'}, {label:'Count', key:'count'}]) + '

Top Services

' + table(d.top_services || [], [{label:'Value', key:'value'}, {label:'Count', key:'count'}]) + '

Local-in Failures

' + table(d.local_in_failures || [], [{label:'Source', key:'src_ip'}, {label:'Service', key:'service'}, {label:'Policy', key:'policy'}, {label:'Count', key:'count'}]); + document.querySelectorAll('details[data-detail-id]').forEach(item => { if (openDetails.has(item.dataset.detailId)) item.open = true; }); } async function loadSettings() { const config = await (await fetch('/api/config', {cache: 'no-store'})).json();