Add manual failures UI and global list filtering
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}`):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user