stream_coverage i status-API.
This commit is contained in:
@@ -114,6 +114,9 @@ async function refresh() {
|
||||
const threat = (data.capabilities || {}).threat_intel || {};
|
||||
const mcp = (data.capabilities || {}).graylog_mcp || {};
|
||||
const configuration = data.configuration || {};
|
||||
const streamCoverage = data.stream_coverage || [];
|
||||
const enabledStreams = streamCoverage.filter(item => item.enabled);
|
||||
const streamsMissingProfile = enabledStreams.filter(item => !item.profile_ready).length;
|
||||
const rawCorrelations = data.cross_source_correlations || [];
|
||||
const correlationsCached = rawCorrelations.length === 0 && uiCache.correlations.length > 0;
|
||||
const correlations = rawCorrelations.length ? rawCorrelations : uiCache.correlations;
|
||||
@@ -143,7 +146,7 @@ async function refresh() {
|
||||
`Baseline sources ready: ${esc((data.baseline || {}).sources_ready || 0)}`
|
||||
].join('<br>');
|
||||
document.getElementById('health').innerHTML = [
|
||||
metric('Baseline sources ready', baseline.sources_ready || 0), metric('MCP events fetched', mcp.events_fetched || 0), metric('Profiles active', (data.stream_profiles || []).length), metric('Correlated entities', correlations.length)
|
||||
metric('Enabled streams', enabledStreams.length), metric('Streams missing profile', streamsMissingProfile), metric('MCP events fetched', mcp.events_fetched || 0), metric('Correlated entities', correlations.length)
|
||||
].join('');
|
||||
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>`;
|
||||
@@ -223,6 +226,7 @@ async function refresh() {
|
||||
const profileNames = Object.fromEntries((data.stream_profiles || []).map(item => [item.stream_id, item.name || item.stream_id]));
|
||||
const profileReadiness = (data.profile_readiness || []).map(item => ({...item, profile_name: item.profile_name || profileNames[item.stream_id] || item.stream_id, stream_title: item.stream_name || item.stream_title || streamTitles[item.stream_id] || item.stream_id}));
|
||||
document.getElementById('diagnostics').innerHTML =
|
||||
'<h3>Stream Coverage</h3>' + table(streamCoverage, [{label:'Stream', key:'stream_name'}, {label:'Enabled', key:'enabled', render:r => r.enabled ? 'yes' : 'no'}, {label:'Profile', render:r => esc(r.profile || 'missing')}, {label:'Entity Field', key:'entity_field'}, {label:'Tracked Fields', key:'tracked_fields'}, {label:'Ready Fields', key:'readiness'}, {label:'Events', key:'events_fetched'}, {label:'Latest Event', key:'latest_event_time'}, {label:'Health', key:'health'}], 'stream-coverage') +
|
||||
'<h3>Cross-Source Correlations</h3>' + table(correlations, [{label:'Entity', key:'entity', render:r => esc(`${r.entity || r.source_ip} (${r.entity_type || 'ip'})`)}, {label:'Streams', render:r => esc((r.streams || []).join(', '))}, {label:'Events', key:'events'}, {label:'Security Events', key:'security_events'}], 'correlations') +
|
||||
'<h3>Entities</h3>' + table(context.source_profiles || [], [{label:'Entity', key:'entity'}, {label:'Events', key:'events'}, {label:'UTM', key:'utm_events'}, {label:'Deny', key:'deny_or_threat_actions'}, {label:'Destinations', key:'distinct_destinations'}, {label:'Actions', render:r => esc((r.top_actions || []).join(', '))}], 'entities') +
|
||||
'<h3>Profile Baseline Readiness</h3>' + table(profileReadiness, [{label:'Profile', key:'profile_name'}, {label:'Stream', key:'stream_title'}, {label:'Field', key:'field'}, {label:'Buckets', key:'buckets'}, {label:'Ready', key:'ready', render:r => r.ready ? 'ready' : 'learning'}], 'profile-readiness') +
|
||||
|
||||
@@ -51,6 +51,47 @@ def _range_seconds(value: object) -> int:
|
||||
return 3600
|
||||
|
||||
|
||||
def _stream_coverage(runtime_values: dict[str, object], stream_profiles: dict[str, object], stream_status: dict[str, object], profile_readiness: list[dict[str, object]], stream_titles: dict[str, str]) -> list[dict[str, object]]:
|
||||
configured = [
|
||||
item for item in runtime_values.get("graylog_streams", [])
|
||||
if isinstance(item, dict) and item.get("id")
|
||||
]
|
||||
status_by_id = {
|
||||
str(item.get("stream_id", "")): item
|
||||
for item in stream_status.get("streams", [])
|
||||
if isinstance(item, dict) and item.get("stream_id")
|
||||
}
|
||||
readiness_by_stream: dict[str, list[dict[str, object]]] = {}
|
||||
for item in profile_readiness:
|
||||
readiness_by_stream.setdefault(str(item.get("stream_id", "")), []).append(item)
|
||||
ids = list(dict.fromkeys([str(item.get("id", "")) for item in configured] + list(stream_profiles) + list(status_by_id)))
|
||||
rows = []
|
||||
for stream_id in ids:
|
||||
profile = stream_profiles.get(stream_id)
|
||||
readiness = readiness_by_stream.get(stream_id, [])
|
||||
ready_fields = sum(1 for item in readiness if item.get("ready"))
|
||||
total_fields = len(readiness)
|
||||
status = status_by_id.get(stream_id, {})
|
||||
enabled = next((bool(item.get("enabled")) for item in configured if str(item.get("id", "")) == stream_id), False)
|
||||
rows.append({
|
||||
"stream_id": stream_id,
|
||||
"stream_name": _stream_name(stream_id, stream_titles, profile),
|
||||
"enabled": enabled,
|
||||
"profile": _profile_name(stream_id, stream_titles, profile) if profile else "",
|
||||
"profile_ready": bool(profile),
|
||||
"entity_field": str(getattr(profile, "entity_field", "")) if profile else "",
|
||||
"tracked_fields": len(getattr(profile, "categorical_fields", ())) + len(getattr(profile, "numeric_fields", ())) if profile else 0,
|
||||
"ready_fields": ready_fields,
|
||||
"total_fields": total_fields,
|
||||
"readiness": f"{ready_fields}/{total_fields}" if total_fields else "0/0",
|
||||
"events_fetched": int(status.get("events_fetched", 0) or 0),
|
||||
"latest_event_time": str(status.get("latest_event_time", "")),
|
||||
"truncated": bool(status.get("truncated")),
|
||||
"health": "not_enabled" if not enabled else "missing_profile" if not profile else "no_events" if int(status.get("events_fetched", 0) or 0) == 0 else "learning" if total_fields and ready_fields < total_fields else "ready" if total_fields else "profile_needs_fields",
|
||||
})
|
||||
return rows
|
||||
|
||||
|
||||
def build_status(
|
||||
log_path: str,
|
||||
*,
|
||||
@@ -146,6 +187,7 @@ def build_status(
|
||||
}
|
||||
for item in profile_readiness
|
||||
]
|
||||
stream_coverage = _stream_coverage(runtime_values, stream_profiles, mcp_status, profile_readiness, stream_titles)
|
||||
intel_ips = sorted(
|
||||
{
|
||||
ip
|
||||
@@ -184,6 +226,7 @@ def build_status(
|
||||
"capabilities": {"threat_intel": threat_intel_status, "graylog_mcp": mcp_status},
|
||||
"configuration": runtime_config,
|
||||
"stream_profiles": [{"stream_id": item.stream_id, "name": _profile_name(item.stream_id, stream_titles, item), "stream_name": _stream_name(item.stream_id, stream_titles, item), "entity_field": item.entity_field, "timestamp_field": item.timestamp_field, "categorical_fields": list(item.categorical_fields), "numeric_fields": list(item.numeric_fields), "detectors": item.detectors, "field_weights": item.field_weights} for item in stream_profiles.values()],
|
||||
"stream_coverage": stream_coverage,
|
||||
"profile_readiness": profile_readiness,
|
||||
"diagnostics": {
|
||||
"top_source_ips": top_field_values(events, "srcip", limit=10),
|
||||
|
||||
Reference in New Issue
Block a user