diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 8e71d0b..05cea8d 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -159,7 +159,9 @@ the address, that rule opens instead. Rules are listed, ordered, turned off and A rule has three parts. **Which messages** -- **Addresses**: an IP, a CIDR (`10.0.7.0/24`) or a pattern, comma-separated. +- **Addresses**: comma-separated, each matched whole: an address (`10.0.7.12`), a CIDR range + (`10.0.7.0/24`) or a pattern (`10.0.7.*`). An address is that device only; a rule naming + `10.0.7.1` does not read `10.0.7.12`. - **Hostnames** and **Applications**: patterns such as `fw-*`, matched against the message as read without the rule. - **Raw text contains**: a regular expression, case-insensitive. Behind a relay every device @@ -232,13 +234,24 @@ Select one or more facilities to filter by the source subsystem. Common faciliti ### Hostname, App Name and Source IP Filters -Enter part of a hostname, an application name or an IP address. Messages whose field contains the -entered text are displayed: +Enter part of a hostname or an application name. Messages whose field contains the entered text are +displayed: - Case does not matter, and neither does an accent on a Latin letter: `cafe` finds `CAFÉ-01`. - Everything else counts as typed, spaces included. `web_01` finds `web_01` and not `web-01`, and `50%` is a percent sign, not a wildcard. +The **Source IP** box takes an address the way a network is described: + +| Typed | Shows the messages from | +|---|---| +| `10.10.1.1` | that address only — not `10.10.1.10` or `10.10.1.100` | +| `10.10.1.0/24` | the CIDR range, here `10.10.1.0` to `10.10.1.255` | +| `10.10.1.*` | the addresses matching the pattern: `*` stands for any characters, `?` for one | +| `10.10.1.` | the addresses containing the text, while it is being typed | + +An IPv6 address matches however it is written: `fe80:0::1` finds `fe80::1`. + ### Search The search box looks in each message's text and in its raw line. The button beside it switches diff --git a/frontend/screenshots/bridge/App.js b/frontend/screenshots/bridge/App.js index 8fc9949..b0e8776 100644 --- a/frontend/screenshots/bridge/App.js +++ b/frontend/screenshots/bridge/App.js @@ -24,7 +24,7 @@ import './runtime.js'; import { isDemo, override, delayed } from './scene.js'; -import { words, filterMatcher, sortMessages, groupMessages } from '../../src/lib/search'; +import { words, filterMatcher, sortMessages, groupMessages, addressMatcher } from '../../src/lib/search'; import { MESSAGES, STATS, SERVER_CONFIG, SERVER_STATUS, ALERT_RULES, ALERT_HISTORY, @@ -103,8 +103,8 @@ let sourceRules = clone(SOURCE_RULES); * applies. Reading a format is the engine's; the demo does not pretend to. */ function trySource(rule, samples) { - const named = (rule.match.sources || []).map((s) => s.trim()).filter(Boolean); - const matches = (ip) => named.some((s) => s === ip || (s.endsWith('.0/24') && ip.startsWith(s.slice(0, -4)))); + const named = (rule.match.sources || []).filter((s) => s.trim()).map((s) => addressMatcher(s, false)); + const matches = (ip) => named.some((take) => take(ip)); return samples.map((s) => { const before = messages.find((m) => m.rawMessage === s.raw) || { ...messages[0], rawMessage: s.raw, message: s.raw, sourceIP: s.sourceIP }; diff --git a/frontend/src/components/FilterBar.svelte b/frontend/src/components/FilterBar.svelte index f2f8ea2..9371dc6 100644 --- a/frontend/src/components/FilterBar.svelte +++ b/frontend/src/components/FilterBar.svelte @@ -210,7 +210,7 @@ + title={$_('filter.sourceIPHint')} on:input={setSourceIP} class="filter-input" /> diff --git a/frontend/src/components/SourceRuleEditor.svelte b/frontend/src/components/SourceRuleEditor.svelte index 00836d1..e4b2518 100644 --- a/frontend/src/components/SourceRuleEditor.svelte +++ b/frontend/src/components/SourceRuleEditor.svelte @@ -7,13 +7,15 @@ * preview is here to prevent. */ import { onDestroy, onMount } from 'svelte'; + import { get } from 'svelte/store'; import { _ } from 'svelte-i18n'; + import { messages } from '../lib/stores'; import { SEVERITY_COLORS } from '../lib/constants'; import { activeZone, formatInZone } from '../lib/timezone'; import { toastError, toastSuccess } from '../lib/toast'; import FormatFields from './FormatFields.svelte'; import { - deleteSourceRule, listText, parseList, ruleDraft, rulesVersion, saveSourceRule, trySourceRule, + deleteSourceRule, listText, parseList, ruleDraft, rulesVersion, samplesForRule, saveSourceRule, trySourceRule, } from '../lib/sources'; import type { SourceRule, SourceSample, SourceTrial } from '../lib/sources'; @@ -28,6 +30,9 @@ rule.levelFrom = rule.levelFrom || ''; rule.encoding = rule.encoding || ''; let sourcesText = listText(rule.match.sources); + // The samples it opened with, until the addresses change: then the + // latest messages from the addresses as now written. + const openedWith = sourcesText; let hostnamesText = listText(rule.match.hostnames); let appNamesText = listText(rule.match.appNames); let hostMode: 'sent' | 'source' | 'fixed' = @@ -62,6 +67,7 @@ clearTimeout(debounce); debounce = setTimeout(async () => { const mine = ++generation; + if (sourcesText !== openedWith) samples = samplesForRule(get(messages), current()); try { const got = samples.length ? await trySourceRule(current(), samples) : []; if (mine !== generation) return; @@ -156,8 +162,9 @@
{$_('sources.which')}
+

{$_('sources.sourcesHint')}