contrinue cleanup UI

This commit is contained in:
larssand
2026-07-02 20:45:32 +02:00
parent 0a9a3d3dd2
commit 75ebaaf307

View File

@@ -124,9 +124,20 @@ HTML = """<!doctype 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 `<button type="button" class="entity-chip ${key === uiCache.selectedCorrelationKey ? 'active' : ''}" data-correlation-key="${esc(key)}">${esc(label)}</button>`;
}).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 `<div class="evidence-item"><div>${esc(line)}</div>${item.graylog_query ? `<code>${esc(item.graylog_query)}</code>${graylogEvidenceLink(item.graylog_query, configuration)}` : ''}</div>`;
const context = item.context || item.message || item.destination || item.service || '-';
const meta = [item.timestamp, item.destination, item.service].filter(Boolean).join(' · ');
return `<div class="evidence-item"><div class="evidence-head"><span class="evidence-stream">${esc(item.stream || 'stream')}</span><span class="evidence-action">${esc(item.action || item.severity || '-')}</span></div><div class="evidence-context">${esc(context)}</div><div class="muted">${esc(meta || '-')}</div>${item.graylog_query ? `<details data-detail-id="${esc(`query:${item.graylog_query}`)}"><summary>Graylog query</summary><code>${esc(item.graylog_query)}</code>${graylogEvidenceLink(item.graylog_query, configuration)}</details>` : ''}</div>`;
}).join('');
const selectedKey = correlationKey(selected);
const selectedLabel = correlationShortLabel(selected);
const keyNote = selectedLabel !== selectedKey ? ` · key ${selectedKey}` : '';
target.innerHTML = `<div class="entity-picker">${chips}</div><div><strong>${esc(selectedLabel || '-')}</strong><div class="muted">${esc((selected.streams || []).join(', ') || 'single stream')} · ${esc(selected.events || 0)} events · ${esc(selected.security_events || 0)} security events${esc(keyNote)}</div></div><div class="evidence-list">${samples || '<p class="muted">No sample evidence for this entity.</p>'}</div>`;
target.innerHTML = `<div class="entity-picker">${chips}</div><div><strong>${esc(selectedLabel || '-')}</strong><div class="muted">${esc((selected.streams || []).join(', ') || 'single stream')}${esc(keyNote)}</div></div><div class="entity-summary"><div class="entity-stat"><b>${esc(selected.events || 0)}</b><span>events</span></div><div class="entity-stat"><b>${esc(selected.security_events || 0)}</b><span>security</span></div><div class="entity-stat"><b>${esc((selected.streams || []).length)}</b><span>streams</span></div></div><div class="evidence-list">${samples || '<p class="muted">No sample evidence for this entity.</p>'}</div>`;
target.querySelectorAll('.entity-chip').forEach(button => button.addEventListener('click', () => {
uiCache.selectedCorrelationKey = button.dataset.correlationKey || '';
renderCorrelationExplorer(window.currentCorrelations || [], window.currentConfiguration || {});
@@ -471,17 +483,18 @@ 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;
if (cw > 760) {
ctx.textAlign='left';
ctx.font='12px Arial';
ctx.fillStyle='#91abc4';
ctx.fillText('Focus entity graph', sideX, sideY);
ctx.fillText('Top correlated', sideX, sideY);
items.slice(0,5).forEach((item,index)=>{
const key=correlationKey(item);
const y=sideY+22+index*23;
@@ -489,6 +502,7 @@ function drawCorrelationGraph(correlations) {
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 => `<button type="button" class="apply-suggested-profile" data-stream-id="${esc(r.stream_id)}">${r.profile_exists ? 'Update profile' : 'Apply profile'}</button>`}
], 'profile-suggestions') : '<p class="muted">No missing stream profiles. Enable "show existing profiles" to inspect already configured profiles.</p>';
const llmText = llm.text ? esc(llm.text).replace(/\\n/g, '<br>') : esc(llm.error || 'LLM assessment disabled or waiting for first run.');
document.getElementById('llmAssessment').innerHTML = `<div>Status: <code>${esc(llm.status || 'unknown')}</code></div><p>${llmText}</p>`;
document.getElementById('llmAssessment').innerHTML = `<div class="ai-box"><details><summary>Status: ${esc(llm.status || (llmEnabled ? 'starting' : 'disabled'))}</summary><p>${llmText}</p></details></div>`;
document.getElementById('anomalies').innerHTML = table(data.anomalies || [], [
{label:'Source', key:'subject', render:r => entityCell(r, 'subject')},
{label:'Score', key:'score'},