Files
ledgrab/plans/activity-log/phase-5-frontend-tab.md
T
alexei.dolgolyov 9a0137fa4c feat(activity-log): phase 5 - Activity tab (smart filtering, live updates, export)
- new top-level Activity tab: filter toolbar (category/severity chips, presets, debounced search, actor/entity/date), keyset load-more, expandable detail
- live prepend via server:activity_logged; authed CSV/JSON blob export
- formatTimestamp/formatRelativeTime in core/ui.ts; history+severity SVG icons; Ctrl+7 shortcut
- i18n activity_log.* across en/ru/zh; getting-started tutorial step; activity-log.css (themed)
- review fixes: newest-first ordering, attribute-context XSS hardening (_escapeAttr + event delegation)
2026-06-09 20:42:44 +03:00

138 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 5: Frontend — Activity tab + smart filtering + live updates
**Status:** ✅ Done
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend · uses the `frontend-design` skill
## Objective
Build the dedicated top-level **Activity** tab: a read-only, smart-filterable,
keyset-paginated log viewer with an entry detail view, live-append of new events, and export.
This is a viewer (Dashboard-style), NOT a CRUD card section.
## Tasks
- [x] `core/ui.ts`: add `formatTimestamp(isoOrMs)` (Today/Yesterday/Date · HH:MM, i18n-aware)
and `formatRelativeTime(isoOrMs)` ("2m ago"), with `tabular-nums` styling guidance for the
list. Reuse existing `time.*` i18n key conventions.
- [x] `features/activity-log.ts`:
- `export async function loadActivityLog()` — fetch first page from
`GET /activity-log` (via `fetchWithAuth`), render the toolbar + list into the panel.
- **Smart filter toolbar:** category (multi, chips), severity (chips), actor
(text input), entity type, date range, free-text search (debounced). Quick presets:
Today / Errors / Auth / Devices. Filters drive server-side query params (no client-side
filtering of a partial page). Re-query on change; reset cursor.
- **List:** one row per entry — severity icon, category badge, relative time (title=absolute),
actor, message, entity crosslink (use `navigateToCard(...)` when the referenced entity is
resolvable). Keyset "load more" (or infinite scroll) using `next_before_seq`.
- **Detail:** expandable row / drawer showing full metadata JSON, exact timestamp, ids.
- **Live append:** `document.addEventListener('server:activity_logged', e => …)` — prepend
the new entry if it passes the active filters; show a subtle "new" affordance. (Depends on
the Phase 2 allowlist entry — already shipped.)
- **Export button:** triggers `GET /activity-log/export?format=…` with current filters via an
authed blob download (use `fetchWithAuth` → blob URL, per frontend.md auth rules).
- Empty / loading / error states; re-render on `languageChanged`.
- [x] Tab wiring:
- `core/tab-registry.ts`: add `activity_log: { loadFnName: 'loadActivityLog', autoRefresh: false }`.
- `templates/index.html`: sidebar tab button (`data-tab`, `switchTab('activity_log')`,
history/clock SVG icon, `data-i18n`) + `<div class="tab-panel" id="tab-activity_log">`.
- `app.ts`: import + `Object.assign(window, { loadActivityLog, … })`; `global.d.ts` decls.
- [x] Icons: add a history/audit icon to `core/icon-paths.ts` + `core/icons.ts`; severity icons
(info/warning/error) reuse existing constants where possible.
- [x] i18n: add `activity_log.*` keys to `static/locales/{en,ru,zh}.json` (title, filter labels,
category/severity names, column labels, presets, empty/error, export, "N entries").
- [x] Tutorials: add an Activity-tab step to the getting-started tour in
`features/tutorials.ts` + `tour.*` keys in all 3 locales.
## Files to Modify/Create
- `server/src/ledgrab/static/js/core/ui.ts` — modified: timestamp/relative-time formatters
- `server/src/ledgrab/static/js/features/activity-log.ts` — NEW: the viewer
- `server/src/ledgrab/static/js/core/tab-registry.ts` — modified: register tab
- `server/src/ledgrab/templates/index.html` — modified: tab button + panel
- `server/src/ledgrab/static/js/app.ts` — modified: import + window globals
- `server/src/ledgrab/static/js/global.d.ts` — modified: window decls
- `server/src/ledgrab/static/js/core/icon-paths.ts` — modified: icons (scrollText, circleAlert, info, filter, xCircle)
- `server/src/ledgrab/static/js/core/icons.ts` — modified: ICON_ACTIVITY_LOG, ICON_SEVERITY_*, ICON_FILTER, ICON_X_CIRCLE
- `server/src/ledgrab/static/locales/{en,ru,zh}.json` — modified: activity_log.* + time.* + tour.activity_log
- `server/src/ledgrab/static/js/features/tutorials.ts` — modified: gettingStartedSteps
- `server/src/ledgrab/static/css/activity-log.css` — NEW: list + toolbar styling
- `server/src/ledgrab/static/css/all.css` — modified: import activity-log.css
## Acceptance Criteria
- [x] New **Activity** tab loads, lists entries, and paginates via keyset "load more".
- [x] Filters hit server-side query params; quick presets work; free-text is debounced.
- [x] New events append live via `server:activity_logged` and respect active filters.
- [x] Export downloads CSV/JSON with auth, honoring current filters.
- [x] Fully localized (en/ru/zh); empty/loading/error states; re-renders on language change.
- [x] No plain `<select>` (use chips); SVG icons only (no emoji).
- [x] `npx tsc --noEmit` clean; `npm run build` succeeds.
## Notes
- **Used the `frontend-design` skill** for the viewer layout, filter toolbar, and detail design.
- Models mirrored: `features/dashboard.ts` (live viewer pattern), `core/events-ws.ts` (WS dispatch),
`core/api.ts` (fetchWithAuth), `core/navigation.ts` (navigateToCard).
- Frontend-only phase — ran `tsc --noEmit` + `npm run build`; did NOT run pytest/ruff.
## Review Checklist
- [x] All tasks completed
- [x] Code follows frontend conventions (i18n ×3, icons, auth fetch, CSS vars)
- [x] No unintended side effects
- [x] Build passes (`tsc --noEmit` + `npm run build`)
- [ ] Manual smoke: tab loads, filters query server, live append, export
## Handoff to Next Phase
### Reusable helpers for Phase 6 (Dashboard widget + Settings panel)
**From `features/activity-log.ts`:**
| Export | Purpose | How Phase 6 uses it |
|--------|---------|---------------------|
| `loadActivityLog()` | Loads the full tab (already registered as the tab loader) | Phase 6 doesn't call this, but it shares state |
| `activityLogToggleDetail(id)` | Expand/collapse an entry row | Dashboard widget can reuse for the Recent Activity list |
| `activityLogExport(format)` | Authed blob download with current filters | Could surface a direct link in the Settings panel |
| `_renderEntryRow(entry, isNew)` | Renders a single entry as an HTML string | Dashboard widget should import this to render its 5-10 most-recent entries |
| `_fetchPage(beforeSeq, append)` | Keyset-paged fetch from `/activity-log` | Dashboard widget calls with `limit=5&categories=…` for its mini-list |
**Note:** `_renderEntryRow` and `_fetchPage` are module-private (underscore prefix). Phase 6 should
either (a) re-export them with public names, or (b) duplicate the minimal render logic for the
compact widget format.
**Recommended approach for Phase 6:** Export two new public helpers:
```typescript
// Add to activity-log.ts
export async function fetchRecentEntries(limit = 5): Promise<ActivityEntry[]>
export function renderCompactEntry(entry: ActivityEntry): string
```
### i18n namespace
All keys are under `activity_log.*`. Time-relative formatters use `time.*` keys.
### CSS classes and tokens introduced
| Class | Purpose |
|-------|---------|
| `.al-panel` | Tab root wrapper |
| `.al-toolbar` | Filter toolbar container |
| `.al-chip` / `.al-chip.active` | Category/severity toggle chips |
| `.al-preset-btn` | Quick-preset buttons |
| `.al-entry` / `.al-entry-row` | Log entry row |
| `.al-detail` / `.al-detail-grid` | Expandable entry detail |
| `.al-cat-*` | Per-category badge colors (auth/device/entity/capture/system) |
| `.al-sev-info/warning/error` | Severity icon color classes |
| `.al-live-dot` | Pulsing green live-update dot |
| `.al-meta-pre` | Scrollable metadata JSON block |
| `.tabular-nums` | `font-variant-numeric: tabular-nums` utility |
### Settings endpoint shape used
Phase 6 Settings panel will call:
- `GET /activity-log/settings``{ enabled: bool, max_days: int, max_entries: int }`
- `PUT /activity-log/settings` → same shape (bounds: enabled=bool, max_days=03650, max_entries=010_000_000)