waf: recent sampled match events on the metrics page#330
Open
acoshift wants to merge 2 commits into
Open
Conversation
Preview
|
Adds the console leg of SPEC-waf-events: a Recent events section on /waf/metrics backed by the new waf.events RPC — newest-first table (time, IP, country, method, host, path, rule, action badge), rule + action filters mirrored into the query string (?ruleId=&action=), keyset load-more pagination (before = result.next, page size 50), and the permanent per-instance sampling caption. Deleted-rule fallback: an event's rule id may match nothing in waf.get (events outlive rules by up to 3 days and waf.set regenerates unknown ids) — such ids render as plain unlinked mono text with a 'rule no longer exists' tooltip, never a dead link. Live rule ids link to their row on /waf/manage via new per-rule row anchors. Events load client-side after hydration, keyed on (project, location) like the metrics fetch, with a token guard discarding stale in-flight pages. Filters map 1:1 onto waf.events params (server-side filtering); a hand-edited ?ruleId= for a deleted rule stays visible in the rule select as a synthetic option instead of a blank trigger. Mock mode grows a waf.events handler with a session-stable seed (ULID-shaped time-ordered ids, RFC 5737 documentation IPs, a since- deleted rule id) mirroring the server's cursor semantics; api.d.ts gains WafEventsRequest/WafEventsResult/WafEvent matching the api-repo contract; countries.ts gains countryName for the country column.
…on tests Review follow-ups on the recent-events panel: - fetchEvents now catches a rejected fetch (offline, connection reset): without it the panel asserted "No events in the last 3 days." — a false claim about the data — instead of the error row. - A failed Load more no longer renders two competing affordances: the Load more button is suppressed while the error row shows, and Try again re-issues the cursor fetch when pages are already loaded instead of discarding them. - Tests: location A -> B same-component navigation refetch (console#303 bug class), initial-load error row + recovery, and load-more failure preserving pages + retrying the cursor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the console workstream of SPEC-waf-events.md (§G, step 5 in the §J rollout table): the
/waf/metrics?project=&location=page gains a Recent events panel under the match chart — recent sampled WAF match events served by the newwaf.eventsRPC.What
table is-variant-compact): time (relative, absolute on hover), action badge (block=danger, log=neutral, allow=success), rule (short id linked to its row on/waf/manage, tooltip = description from the already-loadedwaf.get), IP (mono), country name, method, host, path (mono, truncated, full value intitle).waf.get's current rules), both server-side — they map 1:1 ontowaf.eventsparams — and mirrored into the query string (writable-$derivedURL-params pattern, admin#18) so a filtered view is shareable. A hand-edited?ruleId=naming a deleted rule passes straight through to the RPC and stays visible as<id> (deleted)in the select.waf.setregenerates unknown ids), so an event's rule id may match nothing inwaf.get— such ids render unlinked with a "rule no longer exists" tooltip, never a dead link.before = next, page size 50; the server setsnextonly on an exactly-full page.(project, location)via the params-keyed$effectpattern (console#303) —?location=A → ?location=Breuses this component without a remount and must refetch. Filter/rangereplaceStatewrites don't re-trigger it; stale in-flight responses are discarded via a token bump.Also:
waf/managerule rows gainid="waf-rule-<id>"anchor targets for the event-table links;src/types/api.d.tsmirrors thewaf.eventswire types (request/result/WafEvent); the dev mock (bun dev:mock) seeds ~3 days of events — RFC 5737 documentation IPs only — and replicates the server's exact filter/cursor semantics, including a deleted-rule id.Screenshots
Generated via the Playwright harness against
bun dev:mock(documentation IPs only). The seed data includesold-block-php, a rule that no longer exists in the zone — visible in the panel as the muted, unlinked id.The metrics page with the new panel in context (dark):
Panel close-up (light) — badges, deleted-rule fallback, country names, truncated mono host/path:
Filtered to Block via the server-side action filter (
?action=blockreflected in the URL), dark:Test evidence
tests/waf-events.spec.js— 10 tests against the mock server:?ruleId=passthrough + "(deleted)" select optionbefore = next, appends, button disappears on exhaustion?location=A → ?location=Bsame-component SPA navigation refetches (document reuse asserted via a window marker)Full local verification:
bun lint0 errors ·bun check0 errors · full Playwright suite 341 passed (bun run test).Rollout (SPEC §J — every step inert alone)
This PR is step 5 of 7. Safe order: apiserver migration → apiserver binary → parapet image +
WAF_EVENTS_TOKENSecret → collector envs → console/docs.wafeventring + bearer-authed:9188cursor endpoint + headless Service (feat(waf): sampled match-event ring + direct push to the deploys-app collector moonrhythm/parapet-ingress-controller#182); endpoint off until both envs set, inert until polledCollector.SetWAFEvents+WAF.Eventstypes; tag after merge (this repo consumes the API only via the hand-written type mirror — the api re-pin gate applies to the Go consumers, not console)waf_eventsDDL (apply migration before binary, house rule) +collector.setWAFEvents+waf.eventssyncWAFEvents; enabled only whenwaf_events_target+waf_events_tokenare set (same Secret value as the controller), so the binary can ship earlyNotes
waf-testandwaf-ip-listsare open on this repo and also touchsrc/lib/server/mock.ts,src/types/api.d.ts, andwaf/manage/+page.svelte(adjacent additions — trivial resolution; whichever lands second rebases).(project, location)with no periodic refresh, while the chart above self-refreshes every 60s on short ranges. Spec §G specifies the one-shot load and §A's non-goals exclude live tail; joining the 60s tick would clobber a paginated view mid-inspection, so this PR keeps fetch-once (filters / Try again / navigation all refetch). If "poll-refresh table" in §A meant periodic re-poll, that's a small follow-up.title. Mock and tests use RFC 5737 documentation IPs exclusively.