From 75ebaaf307f2d97345e36b8734a972d6c32e5553e9f0a946f187fbff0c8e0951 Mon Sep 17 00:00:00 2001 From: larssand Date: Thu, 2 Jul 2026 20:45:32 +0200 Subject: [PATCH] contrinue cleanup UI --- src/fgai/dashboard.py | 50 +++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/fgai/dashboard.py b/src/fgai/dashboard.py index 83b9371..10bf63c 100644 --- a/src/fgai/dashboard.py +++ b/src/fgai/dashboard.py @@ -124,9 +124,20 @@ HTML = """ .entity-picker { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; } .entity-chip { border: 1px solid #39709a; background: #08243e; color: #d9e8f7; padding: 6px 8px; cursor: pointer; border-radius: 4px; } .entity-chip.active { border-color: #1ea9ff; color: #f2f8ff; background: #0b3358; } - .evidence-list { display: grid; gap: 8px; max-height: 360px; overflow: auto; padding-right: 4px; } + .entity-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 10px 0; } + .entity-stat { border: 1px solid #163b59; background: #061a2e; border-radius: 6px; padding: 8px; } + .entity-stat b { display: block; color: #f2f8ff; font-size: 18px; } + .entity-stat span { color: #91abc4; font-size: 12px; } + .evidence-list { display: grid; gap: 8px; max-height: 420px; overflow: auto; padding-right: 4px; } .evidence-item { border: 1px solid #163b59; background: #061a2e; border-radius: 6px; padding: 8px; } + .evidence-head { display: flex; gap: 8px; justify-content: space-between; align-items: center; margin-bottom: 5px; } + .evidence-stream { color: #83bce9; font-weight: 700; } + .evidence-action { border: 1px solid #39709a; border-radius: 999px; padding: 2px 6px; color: #d9e8f7; font-size: 12px; } + .evidence-context { color: #c7d7e7; font-size: 13px; line-height: 1.35; word-break: break-word; } .evidence-item code { display: inline-block; margin-top: 4px; } + .ai-box { max-height: 260px; overflow: auto; } + .ai-box details { border: 1px solid #163b59; background: #061a2e; border-radius: 6px; padding: 8px; } + .ai-box summary { cursor: pointer; color: #83bce9; font-weight: 700; } .sort-button { border: 0; background: transparent; color: #83bce9; cursor: pointer; font: inherit; font-weight: 600; padding: 0; } .sort-button:hover { color: #d9e8f7; } .model-list { display: flex; flex-wrap: wrap; gap: 8px; } @@ -327,13 +338,14 @@ function renderCorrelationExplorer(correlations, configuration) { return ``; }).join(''); const samples = (selected.samples || []).slice(0,8).map(item => { - const line = `${item.timestamp || ''} | ${item.stream || ''} | ${item.action || ''} | ${item.destination || ''} | ${item.service || ''} | ${item.context || item.message || ''}`; - return `
${esc(line)}
${item.graylog_query ? `${esc(item.graylog_query)}${graylogEvidenceLink(item.graylog_query, configuration)}` : ''}
`; + const context = item.context || item.message || item.destination || item.service || '-'; + const meta = [item.timestamp, item.destination, item.service].filter(Boolean).join(' · '); + return `
${esc(item.stream || 'stream')}${esc(item.action || item.severity || '-')}
${esc(context)}
${esc(meta || '-')}
${item.graylog_query ? `
Graylog query${esc(item.graylog_query)}${graylogEvidenceLink(item.graylog_query, configuration)}
` : ''}
`; }).join(''); const selectedKey = correlationKey(selected); const selectedLabel = correlationShortLabel(selected); const keyNote = selectedLabel !== selectedKey ? ` · key ${selectedKey}` : ''; - target.innerHTML = `
${chips}
${esc(selectedLabel || '-')}
${esc((selected.streams || []).join(', ') || 'single stream')} · ${esc(selected.events || 0)} events · ${esc(selected.security_events || 0)} security events${esc(keyNote)}
${samples || '

No sample evidence for this entity.

'}
`; + target.innerHTML = `
${chips}
${esc(selectedLabel || '-')}
${esc((selected.streams || []).join(', ') || 'single stream')}${esc(keyNote)}
${esc(selected.events || 0)}events
${esc(selected.security_events || 0)}security
${esc((selected.streams || []).length)}streams
${samples || '

No sample evidence for this entity.

'}
`; target.querySelectorAll('.entity-chip').forEach(button => button.addEventListener('click', () => { uiCache.selectedCorrelationKey = button.dataset.correlationKey || ''; renderCorrelationExplorer(window.currentCorrelations || [], window.currentConfiguration || {}); @@ -471,24 +483,26 @@ function drawCorrelationGraph(correlations) { ctx.fillText(short(label,24), x, y+r+14); if (sub) { ctx.font='11px Arial'; ctx.fillStyle='#91abc4'; ctx.fillText(short(sub,28), x, y+r+28); } }; - drawNode(center.x, center.y, 24, Number(selected.security_events||0) ? '#d95f5f' : '#2389cc', correlationShortLabel(selected), `${Number(selected.events)||0} events / ${Number(selected.security_events)||0} security`, correlationKey(selected)); + drawNode(center.x, center.y, 26, Number(selected.security_events||0) ? '#d95f5f' : '#2389cc', correlationShortLabel(selected), `${Number(selected.events)||0} events / ${Number(selected.security_events)||0} security`, correlationKey(selected)); nodePositions.forEach(node => { const color=node.kind==='stream' ? '#238b5d' : '#6f55c8'; const count=samples.filter(sample => sample.stream===node.name || sampleResource(sample)===node.name).length; - drawNode(node.x,node.y,node.kind==='stream'?15:13,color,node.name,count?`${count} samples`:node.kind,''); + drawNode(node.x,node.y,node.kind==='stream'?15:12,color,node.name,count?`${count}`:node.kind,''); }); const sideX=20, sideY=28; - ctx.textAlign='left'; - ctx.font='12px Arial'; - ctx.fillStyle='#91abc4'; - ctx.fillText('Focus entity graph', sideX, sideY); - items.slice(0,5).forEach((item,index)=>{ - const key=correlationKey(item); - const y=sideY+22+index*23; - ctx.fillStyle=key===correlationKey(selected)?'#1ea9ff':'#91abc4'; - ctx.fillText(`${short(correlationShortLabel(item),24)} ${Number(item.security_events||0)}/${Number(item.events||0)}`, sideX, y); - uiCache.correlationHitboxes.push({key,x:sideX-4,y:y-14,w:210,h:20}); - }); + if (cw > 760) { + ctx.textAlign='left'; + ctx.font='12px Arial'; + ctx.fillStyle='#91abc4'; + ctx.fillText('Top correlated', sideX, sideY); + items.slice(0,5).forEach((item,index)=>{ + const key=correlationKey(item); + const y=sideY+22+index*23; + ctx.fillStyle=key===correlationKey(selected)?'#1ea9ff':'#91abc4'; + ctx.fillText(`${short(correlationShortLabel(item),24)} ${Number(item.security_events||0)}/${Number(item.events||0)}`, sideX, y); + uiCache.correlationHitboxes.push({key,x:sideX-4,y:y-14,w:210,h:20}); + }); + } ctx.textAlign='left'; ctx.fillStyle='#91abc4'; ctx.font='12px Arial'; ctx.fillText('Selected entity is centered. Green nodes are streams; purple nodes are destinations/services. Click a listed entity or center node evidence chip to change focus.', 8, ch-14); } function showRefreshError(error) { @@ -600,7 +614,7 @@ async function refresh() { {label:'Action', render:r => ``} ], 'profile-suggestions') : '

No missing stream profiles. Enable "show existing profiles" to inspect already configured profiles.

'; const llmText = llm.text ? esc(llm.text).replace(/\\n/g, '
') : esc(llm.error || 'LLM assessment disabled or waiting for first run.'); - document.getElementById('llmAssessment').innerHTML = `
Status: ${esc(llm.status || 'unknown')}

${llmText}

`; + document.getElementById('llmAssessment').innerHTML = `
Status: ${esc(llm.status || (llmEnabled ? 'starting' : 'disabled'))}

${llmText}

`; document.getElementById('anomalies').innerHTML = table(data.anomalies || [], [ {label:'Source', key:'subject', render:r => entityCell(r, 'subject')}, {label:'Score', key:'score'},