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'},