Add manual failures UI and global list filtering

This commit is contained in:
2026-02-23 17:44:05 +03:00
parent 8aa8b26184
commit eba3b60b48
18 changed files with 2429 additions and 221 deletions

View File

@@ -100,6 +100,18 @@ History API invariants:
- `GET /failures`
- `POST /failures`
`POST /failures` (manual UI registration) request:
- `component_serial` (required, exact component serial)
- `server_serial` (optional, used for validation/binding; submit without component serial is rejected)
- `failure_date` (required, `YYYY-MM-DD`)
- `description` (optional)
`POST /failures` behavior:
- resolves component by serial
- records component failure status via history (`COMPONENT_STATUS_SET` -> `FAILED`)
- writes/updates `failure_events` projection row with source `manual_ui`
- returns resolved component/server summary and created IDs
## UI Routes
- `GET /ui`
@@ -114,6 +126,9 @@ History API invariants:
- `GET /ui/component/{vendor}/{model}` (model statistics page)
- `GET /ui/component/{vendor}/{model}/{vendor_serial}`
- `GET /ui/failure`
- `GET /ui/failure/{failure_id}` (failure detail page)
- `GET /ui/failures`
- `GET /ui/failures/{failure_id}` (compatibility alias to failure detail page)
- `GET /ui/ingest`
- `GET /ui/ingest/manual-template.csv`
- `GET /ui/history-admin`
@@ -121,6 +136,7 @@ History API invariants:
UI/admin notes:
- Destructive registry action `DELETE /registry/assets/{id}` ("Asset -> Delete with details") is operated from the Data Admin UI, not from the regular asset page.
- UI routes are **semantic-first and singular** (`/ui/asset`, `/ui/component`, `/ui/failure`); legacy plural/ID-based UI fallback routes are intentionally removed.
- Failure detail page supports both singular and plural ID routes for compatibility (`/ui/failure/{id}` primary, `/ui/failures/{id}` alias).
## CSV Export Contract

View File

@@ -56,6 +56,18 @@ For critical components:
- `failure_events.external_id` includes the same failure timestamp
- `failure_events` is a projection and may be rebuilt from component history (`COMPONENT_STATUS_SET`)
Manual UI failure registration invariants:
- `POST /failures` is history-backed for component status (`COMPONENT_STATUS_SET` -> `FAILED`)
- manual failure registration writes status history event and `failure_events` projection in one DB transaction
- manual failures use a dedicated projection source (`manual_ui`) so history recompute cleanup for ingest/history sources does not delete them
- `failure_date` from UI is normalized to start-of-day UTC timestamp for `failure_events.failure_time` in v1
- failure detail `open duration` is computed with date precision (UTC day difference from failure date to repair date / current date), not timestamp precision
Active failure / repair-by-replacement semantics (Failures UI):
- active failure = failure event without later replacement install in the same slot on the same server
- repair by replacement is derived from `installations` history: later install of a different component (`part_id != failed part_id`) into same `machine_id + slot_name`
- if slot at failure time cannot be resolved from installation history (e.g. manual failure date normalized to start of day), UI may fallback to current installation slot for display-only slot rendering in `Active Failures`
## First Seen Rules
`parts.first_seen_at` must be the earliest known ingest-derived component time.

View File

@@ -136,6 +136,53 @@ Component card editing contract:
- `Save` persists changes through `PUT /registry/components/{id}`.
- `Cancel` reverts unsaved form values.
## Failures Page
Route: `GET /ui/failures`
Required section order:
1. `Active Failures`
2. `Failure Chronology`
`Active Failures` lists unresolved failure incidents (no later replacement in the same slot on the same server).
`Failure Chronology` includes:
- failure events
- repair-by-replacement derived entries (new component installed in the same slot on the same server after failure)
Failure detail page:
- Routes:
- primary: `GET /ui/failure/{failure_id}`
- compatibility alias: `GET /ui/failures/{failure_id}`
- `Active Failures` table links from `Failure Time` and `Details` open the failure detail page.
- Page aggregates incident context:
- server and failed component
- component install time into server (when available)
- failure time
- repair-by-replacement details (if detected)
- open duration
- `Repair / Replacement` section must present replacement comparison as paired rows:
- `Model (old / new)`
- `Serial (old / new)`
- Avoid redundant duplicate serial rendering across summary blocks when the same identifiers are already shown in the repair comparison.
- `Open Duration` on failure detail page is date-precision (whole days), not hour/minute precision.
Manual failure registration UI contract:
- page header includes `Register Failure` action button
- opens modal for manual failure input
- fields are text inputs with suggestions (datalist/autocomplete style):
- server serial
- location
- component serial (suggestion label includes model on the same line)
- failure date (`YYYY-MM-DD`, default = today)
- description
- component serial is required to submit
- if component serial is recognized and current installation is known, server serial is auto-filled
- if location is selected and maps uniquely, component serial is auto-filled; if ambiguous, UI shows candidate list for explicit selection
- server serial and location suggestions/filtering are linked (server narrows location suggestions)
- submit requires explicit confirmation step showing entered and recognized data before request is sent
## List Pages
Creation flows:
@@ -151,8 +198,42 @@ Shared list behavior:
- Query contract: page number is controlled through URL query (`page` for list pages, section-specific keys for multi-list pages such as dashboard).
- Every table header must provide per-column text filtering:
- A filter input is rendered under each column header.
- Input suggestions are populated from current values visible in that table (current page/slice).
- Filtering is case-insensitive and applies as substring match.
- Filtering semantics are global for the list/query scope: filters apply to the full result set before pagination, not only to the currently rendered page/slice.
- Page-local header filtering (client-side filtering over only visible rows of a paginated table) is not allowed for primary list pages because it produces misleading results.
- Input suggestions/datalists should be derived from the full filtered list/query scope (or a server-provided option set for that scope), not only from the current page.
- Changing any header filter resets the corresponding pagination query parameter to page 1.
List behavior checklist (page-by-page):
- `/ui/asset` (assets list / `All Assets`):
- per-column filters: server-side, global scope (full dataset before pagination)
- pagination: single list (`page`)
- selection: global across pages (persistent client storage)
- `/ui/component` (components list):
- per-column filters: server-side, global scope (full dataset before pagination)
- pagination: single list (`page`)
- selection: none (row navigation only)
- `/ui/failure` and `/ui/failures` (failures page):
- `Active Failures`: paginated section (`active_page`)
- `Failure Chronology`: paginated section (`chronology_page`)
- page-local auto header filters are disabled on paginated tables unless replaced with server-side global filters for that section
- manual registration modal suggestions are derived from page data / server-provided option sets, not from visible rows
- `/ui/search` (global search):
- result sections (`Assets`, `Components`, `Failure events`) use independent pagination
- generic page-local auto header filters are disabled on paginated result tables
- if/when header filters are added to search sections, they must be server-side and section-scoped before pagination
- `/ui` (dashboard):
- dashboard tables/lists use independent pagination per section
- generic page-local auto header filters are disabled on paginated dashboard tables
- any future header filters must be server-side and applied before section pagination
- `/ui/component/{vendor}/{model}` (model stats page):
- `Servers With Installed Components Of This Model`: independent pagination (`servers_page`)
- `Components Of This Model`: independent pagination (`components_page`)
- generic page-local auto header filters are disabled on both paginated tables unless replaced with server-side global filters
- `/ui/asset/{id}` and `/ui/component/{id}` (detail pages):
- embedded historical tables may be unpaginated
- local-only filtering is acceptable only for explicitly unpaginated tables; paginated tables on detail pages must follow the shared global-filter rule
Assets list specifics:
@@ -160,6 +241,7 @@ Assets list specifics:
- `All Assets` supports global bulk selection:
- Header `Select` checkbox can select all assets across all pagination pages.
- Selected IDs persist across page navigation and are stored in browser local storage.
- Global-selection semantics for paginated list bulk actions must be explicit and must not silently degrade to current-page-only selection.
Component model statistics page (`/ui/component/{vendor}/{model}`):