första UI/operatörs-pass

This commit is contained in:
larssand
2026-07-02 19:47:09 +02:00
parent 009c54b206
commit 7568767946
3 changed files with 181 additions and 14 deletions

View File

@@ -73,6 +73,71 @@ The UI also includes a `How To` tab with the same operational checklist. Use it
when adding new streams or when the dashboard has data but it is unclear what
needs attention next.
## Operating The Dashboard
Start in `Overview`, not in the long diagnostics tables. The intended daily flow
is:
1. Read `Operator Guidance` for the next action SignalScope thinks is most useful.
2. Check `Investigation Incidents` for grouped entity-level problems.
3. Use the correlation graph to see whether the same entity appears across
several streams.
4. Open `Findings` only after you have an entity or incident to inspect.
5. Expand evidence rows and use the Graylog query link to inspect the raw events.
6. Mark the finding as `Expected`, `False positive`, or `Confirmed`.
The top status badges are health indicators:
- `Graylog MCP: connected` means the last completed MCP query worked.
- `Graylog MCP: refreshing` means a poll is in progress and the UI is showing
previous counters until the poll completes.
- `Graylog MCP: error` means the UI may be showing cached data. Check the MCP
URL, token, DNS/TLS, and stream permissions.
- `Baseline: N sources ready` means historical source-IP baselines exist for
`N` source entities. Profile readiness is checked separately per stream field.
- `Ollama: cached` or `ok` means local LLM output is available. It is supporting
evidence, not the source of truth.
Use `Diagnostics -> Stream Coverage` to decide whether the monitor is healthy:
- `ready`: stream is enabled, profile exists, events are arriving, and profile
fields have enough baseline history.
- `learning`: stream is enabled and profiled, but baseline age or bucket count is
still too low.
- `missing_profile`: stream is enabled but no profile exists. Apply a
recommended profile or edit one manually.
- `no_events`: stream is enabled but no raw sample was returned in the last poll.
Check stream activity, query, range, or permissions.
- `partial_fetch`: Graylog returned only part of the raw sample. Use aggregate or
auto mode with a smaller raw sample on high-EPS streams.
- `sample capped`: aggregate mode counted the full window, but raw events were
intentionally capped to keep context queries manageable.
`Ready Fields` is shown as `ready/tracked`, for example `0/8`. The stream profile
tracks 8 fields, but none of those fields are mature yet. A field needs at least
12 five-minute buckets and the configured `Baseline training days` before it is
ready. With the default seven-day training window, new profiles can show `0/X`
for days even while data is being learned.
Interpret findings by maturity:
- During `learning`, findings are mostly profile-tuning signals.
- When fields are `ready`, high-score deviations are more meaningful.
- `new_relationship` means a new field pair appeared, such as
`username -> srcip` or `host -> process.name`.
- `rare_value` means a new value appeared for a profiled entity and field.
- `event_rate_burst`, `auth_failure_burst`, `dns_query_burst`, and
`deny_action_burst` compare the current window to historical buckets.
The review buttons are part of the detection loop:
- `Expected`: known behavior that should stay visible but not keep creating
noise for the same scope.
- `False positive`: weak or bad signal for this scoped pattern.
- `Confirmed`: real investigation item.
- Expiry should be used for temporary expected changes, such as maintenance or a
migration window.
## Primary Workflow: Graylog MCP
Graylog 7.1 MCP is the primary log-source integration. In the dashboard, open

File diff suppressed because one or more lines are too long

View File

@@ -36,6 +36,14 @@ class DashboardTests(unittest.TestCase):
self.assertIn("Save streams", HTML)
self.assertIn("function saveStreamSelection", HTML)
def test_dashboard_includes_operator_guidance(self):
self.assertIn("Operator Guidance", HTML)
self.assertIn("function buildActionPlan", HTML)
def test_dashboard_links_evidence_to_graylog(self):
self.assertIn("function graylogEvidenceLink", HTML)
self.assertIn("/search?rangetype=relative", HTML)
if __name__ == "__main__":
unittest.main()