Add the filterable event log panel
React · React · advanced · greenfield
Adds the event log panel to the ops dashboard: status dropdown, text filter over source and message, first 200 matches rendered with a total count, and a live 'Updated Ns ago' freshness indicator. The filter over the full event window is the heavy part, so the filtered list is memoized with useMemo and only the first 200 rows hit the DOM. Verified the filters and the ticking freshness label against a seeded window.
An internal ops dashboard, typically left open on a wall monitor and in background tabs for hours. The parent streams `events` in as one stable array reference per refresh (a few times a minute) and passes `refreshedAt` for the freshness label.
Requirements
- The ops dashboard gets an event log panel: it filters an in-memory window of up to 50,000 events by a status dropdown and a case-insensitive text query matched against each event's source and message, and renders the first 200 matches plus a "Showing X of Y matching events" count.
- Filtering the full window is the expensive operation. It must run only when `events`, the selected status, or the query text actually changes — never on unrelated re-renders. Memoize it.
- The header shows freshness — "Updated Ns ago" computed from the `refreshedAt` prop — ticking once per second while the panel is mounted, with the interval cleaned up on unmount. This ticker re-renders the component every second by design.
- No virtualization library: capping the rendered list at 200 rows is the agreed approach for keeping the DOM small.
Files touched
- src/components/EventLog.jsx
--- src/components/EventLog.jsx
+import { useEffect, useMemo, useState } from 'react';
+
+const STATUSES = ['all', 'ok', 'warning', 'error'];
+const MAX_ROWS = 200;
+
+function filterEvents(events, criteria) {
+ const query = criteria.query.trim().toLowerCase();
+ return events.filter((event) => {
+ if (criteria.status !== 'all' && event.status !== criteria.status) {
+ return false;
+ }
+ if (query === '') {
+ return true;
+ }
+ return (
+ event.source.toLowerCase().includes(query) ||
+ event.message.toLowerCase().includes(query)
+ );
+ });
+}
+
+function secondsAgo(timestamp, now) {
+ return Math.max(0, Math.floor((now - timestamp) / 1000));
+}
+
+/**
+ * Event log panel for the ops dashboard. `events` is the full in-memory
+ * window (tens of thousands of rows); filtering it is the expensive part,
+ * so the filtered list is memoized. Only the first MAX_ROWS matches render.