Fixed both issues
This commit is contained in:
@@ -414,6 +414,14 @@ function drawCorrelationGraph(correlations) {
|
|||||||
const canvas=document.getElementById('correlationGraph'), ctx=canvas.getContext('2d'), ratio=window.devicePixelRatio||1, cw=canvas.clientWidth, ch=canvas.clientHeight;
|
const canvas=document.getElementById('correlationGraph'), ctx=canvas.getContext('2d'), ratio=window.devicePixelRatio||1, cw=canvas.clientWidth, ch=canvas.clientHeight;
|
||||||
canvas.width=cw*ratio; canvas.height=ch*ratio; ctx.scale(ratio,ratio); ctx.clearRect(0,0,cw,ch);
|
canvas.width=cw*ratio; canvas.height=ch*ratio; ctx.scale(ratio,ratio); ctx.clearRect(0,0,cw,ch);
|
||||||
const short=(value,max=22)=>String(value||'-').length>max?`${String(value).slice(0,max-3)}...`:String(value||'-');
|
const short=(value,max=22)=>String(value||'-').length>max?`${String(value).slice(0,max-3)}...`:String(value||'-');
|
||||||
|
const usefulResource = value => {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text || ['-', '0', 'unknown', 'n/a', 'none', 'null'].includes(text.toLowerCase())) return '';
|
||||||
|
if (/^\\d+(\\.\\d+)?$/.test(text)) return '';
|
||||||
|
if (/^\\d{4}-\\d{2}-\\d{2}t/i.test(text)) return '';
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
const sampleResource = item => usefulResource(item.destination) || usefulResource(item.service) || usefulResource(item.type) || usefulResource(item.context);
|
||||||
const items=[...(correlations||[])].sort((a,b)=>(Number(b.security_events)||0)-(Number(a.security_events)||0) || (Number(b.events)||0)-(Number(a.events)||0) || (b.streams||[]).length-(a.streams||[]).length).slice(0,8);
|
const items=[...(correlations||[])].sort((a,b)=>(Number(b.security_events)||0)-(Number(a.security_events)||0) || (Number(b.events)||0)-(Number(a.events)||0) || (b.streams||[]).length-(a.streams||[]).length).slice(0,8);
|
||||||
uiCache.correlationHitboxes = [];
|
uiCache.correlationHitboxes = [];
|
||||||
if (!items.length) { ctx.fillStyle='#91abc4'; ctx.font='14px Arial'; ctx.fillText('No multi-stream entities in the current analysis window.', 16, 28); return; }
|
if (!items.length) { ctx.fillStyle='#91abc4'; ctx.font='14px Arial'; ctx.fillText('No multi-stream entities in the current analysis window.', 16, 28); return; }
|
||||||
@@ -424,7 +432,7 @@ function drawCorrelationGraph(correlations) {
|
|||||||
const center={x:cw*.48,y:ch*.48};
|
const center={x:cw*.48,y:ch*.48};
|
||||||
const streams=(selected.streams||[]).slice(0,7);
|
const streams=(selected.streams||[]).slice(0,7);
|
||||||
const samples=(selected.samples||[]).slice(0,8);
|
const samples=(selected.samples||[]).slice(0,8);
|
||||||
const resources=[...new Set(samples.map(item => item.destination || item.service || item.type || '').filter(Boolean))].slice(0,5);
|
const resources=[...new Set(samples.map(sampleResource).filter(Boolean))].slice(0,5);
|
||||||
const outer=[...streams.map(name=>({kind:'stream', name})), ...resources.map(name=>({kind:'resource', name}))];
|
const outer=[...streams.map(name=>({kind:'stream', name})), ...resources.map(name=>({kind:'resource', name}))];
|
||||||
const radius=Math.max(86, Math.min(cw,ch)*.32);
|
const radius=Math.max(86, Math.min(cw,ch)*.32);
|
||||||
ctx.strokeStyle='#163b59';
|
ctx.strokeStyle='#163b59';
|
||||||
@@ -466,7 +474,7 @@ function drawCorrelationGraph(correlations) {
|
|||||||
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, 24, 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 => {
|
nodePositions.forEach(node => {
|
||||||
const color=node.kind==='stream' ? '#238b5d' : '#6f55c8';
|
const color=node.kind==='stream' ? '#238b5d' : '#6f55c8';
|
||||||
const count=samples.filter(sample => sample.stream===node.name || sample.destination===node.name || sample.service===node.name || sample.type===node.name).length;
|
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:13,color,node.name,count?`${count} samples`:node.kind,'');
|
||||||
});
|
});
|
||||||
const sideX=20, sideY=28;
|
const sideX=20, sideY=28;
|
||||||
@@ -532,9 +540,10 @@ async function refresh() {
|
|||||||
metric('Anomalies high+', (a.high || 0) + (a.critical || 0))
|
metric('Anomalies high+', (a.high || 0) + (a.critical || 0))
|
||||||
].join('');
|
].join('');
|
||||||
const llm = data.llm_assessment || {};
|
const llm = data.llm_assessment || {};
|
||||||
|
const llmEnabled = Boolean(llm.enabled || configuration.llm_enabled);
|
||||||
document.getElementById('capabilities').innerHTML = [
|
document.getElementById('capabilities').innerHTML = [
|
||||||
capability('Baseline', baseline.enabled ? 'on' : 'warn', baseline.enabled ? `${baseline.sources_ready || 0} sources ready` : 'disabled'),
|
capability('Baseline', baseline.enabled ? 'on' : 'warn', baseline.enabled ? `${baseline.sources_ready || 0} sources ready` : 'disabled'),
|
||||||
capability('Ollama', llm.enabled && llm.status !== 'error' ? 'on' : 'warn', llm.enabled ? (llm.status || 'starting') : 'disabled'),
|
capability('Ollama', llmEnabled && llm.status !== 'error' ? 'on' : 'warn', llmEnabled ? (llm.status || 'starting') : 'disabled'),
|
||||||
capability('Threat Intel', threat.enabled && threat.configured ? 'on' : 'warn', threat.enabled ? `${threat.provider || 'unknown'}${threat.configured ? '' : ', key missing'}` : 'disabled'),
|
capability('Threat Intel', threat.enabled && threat.configured ? 'on' : 'warn', threat.enabled ? `${threat.provider || 'unknown'}${threat.configured ? '' : ', key missing'}` : 'disabled'),
|
||||||
capability('Graylog MCP', mcp.status === 'connected' ? 'on' : 'warn', configuration.log_source === 'graylog_mcp' ? (mcp.status || 'checking') : 'not selected')
|
capability('Graylog MCP', mcp.status === 'connected' ? 'on' : 'warn', configuration.log_source === 'graylog_mcp' ? (mcp.status || 'checking') : 'not selected')
|
||||||
].join('');
|
].join('');
|
||||||
|
|||||||
@@ -578,5 +578,7 @@ def monitor_loop(
|
|||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
status["llm_assessment"] = {"enabled": False, "status": "disabled", "text": ""}
|
status["llm_assessment"] = {"enabled": False, "status": "disabled", "text": ""}
|
||||||
|
if status_cache_path and isinstance(mcp, dict) and mcp.get("status") not in {"error", "refreshing"}:
|
||||||
|
StatusSnapshotStore(status_cache_path).save("last_good", status)
|
||||||
write_status(status, output)
|
write_status(status, output)
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class MonitorTests(unittest.TestCase):
|
|||||||
"summary": {"total": 1234},
|
"summary": {"total": 1234},
|
||||||
"capabilities": {"graylog_mcp": {"status": "connected", "events_fetched": 99, "raw_events_fetched": 88, "aggregate_events": 1234, "fetch_mode": "aggregate", "coverage_status": "complete_window"}},
|
"capabilities": {"graylog_mcp": {"status": "connected", "events_fetched": 99, "raw_events_fetched": 88, "aggregate_events": 1234, "fetch_mode": "aggregate", "coverage_status": "complete_window"}},
|
||||||
"cross_source_correlations": [{"entity": "10.0.0.1", "entity_label": "host01 (10.0.0.1)"}],
|
"cross_source_correlations": [{"entity": "10.0.0.1", "entity_label": "host01 (10.0.0.1)"}],
|
||||||
|
"llm_assessment": {"enabled": True, "status": "cached", "text": "previous assessment"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,6 +96,7 @@ class MonitorTests(unittest.TestCase):
|
|||||||
self.assertEqual(mcp["raw_events_fetched"], 88)
|
self.assertEqual(mcp["raw_events_fetched"], 88)
|
||||||
self.assertEqual(mcp["aggregate_events"], 1234)
|
self.assertEqual(mcp["aggregate_events"], 1234)
|
||||||
self.assertEqual(mcp["previous_status"], "connected")
|
self.assertEqual(mcp["previous_status"], "connected")
|
||||||
|
self.assertTrue(status["llm_assessment"]["enabled"])
|
||||||
self.assertTrue(status["status_cache"]["served_from_cache"])
|
self.assertTrue(status["status_cache"]["served_from_cache"])
|
||||||
|
|
||||||
def test_profile_readiness_includes_stream_and_profile_names(self):
|
def test_profile_readiness_includes_stream_and_profile_names(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user