92 lines
5.4 KiB
Markdown
92 lines
5.4 KiB
Markdown
# Phase 5: Frontend — Activity tab + smart filtering + live updates
|
||
|
||
**Status:** ⬜ Not Started
|
||
**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
|
||
|
||
- [ ] `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.
|
||
- [ ] `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, IconSelect/chips), severity (chips), actor
|
||
(EntitySelect/text), 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`.
|
||
- [ ] 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.
|
||
- [ ] Icons: add a history/audit icon to `core/icon-paths.ts` + `core/icons.ts`; severity icons
|
||
(info/warning/error) reuse existing constants where possible.
|
||
- [ ] 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").
|
||
- [ ] 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` — modify: 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` — modify: register tab
|
||
- `server/src/ledgrab/templates/index.html` — modify: tab button + panel
|
||
- `server/src/ledgrab/static/js/app.ts` — modify: import + window globals
|
||
- `server/src/ledgrab/static/js/global.d.ts` — modify: window decls
|
||
- `server/src/ledgrab/static/js/core/icon-paths.ts` / `core/icons.ts` — modify: icons
|
||
- `server/src/ledgrab/static/locales/{en,ru,zh}.json` — modify: i18n keys
|
||
- `server/src/ledgrab/static/js/features/tutorials.ts` — modify: tour step
|
||
- `server/src/ledgrab/static/css/*` — modify/new: list + toolbar styling (follow base.css vars)
|
||
|
||
## Acceptance Criteria
|
||
|
||
- New **Activity** tab loads, lists entries, and paginates via keyset "load more".
|
||
- Filters hit server-side query params; quick presets work; free-text is debounced.
|
||
- New events append live via `server:activity_logged` and respect active filters.
|
||
- Export downloads CSV/JSON with auth, honoring current filters.
|
||
- Fully localized (en/ru/zh); empty/loading/error states; re-renders on language change.
|
||
- No plain `<select>` (use IconSelect/EntitySelect/chips); SVG icons only (no emoji).
|
||
- `npx tsc --noEmit` clean; `npm run build` succeeds.
|
||
|
||
## Notes
|
||
|
||
- **Use the `frontend-design` skill** for the viewer layout, filter toolbar, and detail design
|
||
— aim for a polished, distinctive result consistent with the app's design language and CSS
|
||
variables in `static/css/base.css` (`--primary-color`, `--card-bg`, etc.).
|
||
- Models to mirror: `features/dashboard.ts` (non-card live viewer + load pattern),
|
||
`core/events-ws.ts` (`server:<type>` dispatch), `core/entity-palette.ts` (EntitySelect),
|
||
`core/icon-select.ts` (IconSelect). Auth/blob download rules: `contexts/frontend.md`.
|
||
- Frontend-only phase → run `tsc --noEmit` + `npm run build`; do NOT run pytest/ruff.
|
||
|
||
## Review Checklist
|
||
|
||
- [ ] All tasks completed
|
||
- [ ] Code follows frontend conventions (i18n ×3, icons, selectors, auth fetch, CSS vars)
|
||
- [ ] No unintended side effects
|
||
- [ ] Build passes (`tsc --noEmit` + `npm run build`)
|
||
- [ ] Manual smoke: tab loads, filters query server, live append, export
|
||
|
||
## Handoff to Next Phase
|
||
|
||
<!-- Filled in by the implementer: the activity-log render/append functions Phase 6's
|
||
Dashboard widget can reuse, the i18n namespace, and the settings endpoint shape used. -->
|