Skip to content

feat(assets): inventory on the redesign layout (#906) - #936

Merged
alex-dembele merged 3 commits into
masterfrom
feat/906-assets
Oct 12, 2026
Merged

alex-dembele merged 3 commits into
masterfrom
feat/906-assets

Conversation

@alex-dembele

@alex-dembele alex-dembele commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Seventh screen of the redesign epic (#899). Stacked on #933 (activity): the base is feat/905-activity, so the diff shows only this issue. First PR checked against the owner's review (D-069) and its validation checklist.

What changes for the user

The inventory is the dense table of the mockup:

  • Header: "12 actifs · 7 critiques · couverture des agents non mesurée", with "Voir la topologie" and "Lancer une découverte".
  • Filters: category tabs with counts (Serveurs, Postes, Applications, Bases de données, Réseau, Cloud…), then one toolbar line: search, a "KEV seulement" toggle, the attribute search, filters, columns and export.
  • Columns: asset (name with hostname / resource id / address), type with icon, criticality, owner, location, risks, vulnerabilities with a KEV tag, last detection.
  • Rows: a click opens the asset's drawer.

"Lancer une découverte" runs a real scan:

  • One enabled source: it starts the scan and the toast's "Suivre" opens the job.
  • Several sources: it asks which one.
  • No source: it goes to Infrastructure to set one up.
  • Who sees it: only members with scanner:scan.

Backend

  • GET /assets/exposure: per asset with a finding, the open vulnerabilities, the open KEV findings and the last detection. One grouped, tenant-scoped query.
    • Guard: vulnerabilities:read, separate from the inventory, so reading assets does not reveal vulnerabilities.
    • Isolation: decision registered, with a test that seeds a second tenant's finding on the same asset id.
    • API contract: OpenAPI updated.
  • last_detected_at is the latest last_seen of any finding, resolved ones included. It is not a last-scan date: a scan that finds nothing leaves no row. The UI calls it "Dernière détection" and explains it in a tooltip.
  • MAX(timestamp) loses its type on SQLite, so the query casts it to text and the repository parses either database's format. The live Postgres output matched the same SQL run by hand.

Verified

  • go test ./... all packages pass. New tests: TestAssetExposure_Success / _NotFound / _Unauthorized, TestExposureByAsset_Success / _NotFound, and the isolation ratchet.
  • npx tsc -b --noEmit clean, eslint and prettier clean on the touched files, npx vitest run 117 files / 1054 tests.
  • New inventoryPage.test.tsx (9 tests) and inventoryRow.test.ts (4) cover:
    • summary, columns, KEV tag and type pills;
    • no exposure request for a member without vulnerabilities:read;
    • a row opens the drawer;
    • discovery with no source and with one source;
    • error and empty states.
  • Live on Postgres with the parity dataset:
    • 1440 px dark and light, 1280 px light, and 600 px (the narrowest this browser allows), side by side with the design. No sideways page scroll at any width; at 600 px the table scrolls inside its frame.
    • A row click opened /assets?drawer=asset&entity=….
    • Discovery with no source went to /infrastructure.
    • No console errors.

Deliberate differences from the mockup

  • Agent coverage is "non mesurée": no agent reports per-asset inventory until feat(backend): find vulnerabilities automatically from the agent's package inventory #910 (D-069 E, no invented figure).
  • "Dernière détection" replaces "Dernier scan", for the reason above.
  • Criticality uses our four labels (Critique / Élevée / Moyenne / Faible), the same badge as everywhere else (D-069 A spirit: one format).
  • Kept from the old page so nothing is lost and group actions still work:
    • the typed attribute search, behind a toggle;
    • selection with governed bulk delete;
    • the criticality filter, the column chooser and export;
    • edit / history / delete per row;
    • creation, import and attribute schemas in the "…" menu;
    • Score and Catégorie columns, hidden by default.
  • Table height: the body scrolls inside the table frame, as on every register (the shared DataTable needs it for virtualisation); the mockup lets the page scroll.
  • Tabs instead of pills: the mockup's type pills became the design-system tabs on schema categories, after the owner found the pills badly arranged and a design critique agreed. That's eleven pills over two lines, nine of them "1", now replaced by one tab line plus a one-line toolbar at 1440 and 1280. Free-text types stay in the Type column.

Not done here

  • The live check used the admin account only. The read-only paths (no vulnerability access, no scan rights) are covered by tests with the real permission gate.
  • I added parity owners and locations directly in the throwaway database for the screenshots; real tenants show whatever they have entered.

Closes #906

The redesigned inventory shows, on each asset, its open vulnerabilities
with a KEV tag and when a scan last reported something on it. GET
/assets/exposure returns, per asset with a finding, the unresolved
count, the unresolved KEV count and the last detection, from one
grouped, tenant-scoped query.

It is its own read, guarded by vulnerabilities:read: a member who may
read assets but not vulnerabilities does not learn them through the
inventory. The last detection is the latest last_seen of any finding,
resolved ones included; it is not a last-scan date, since a scan that
finds nothing leaves no row, and the contract says so.

MAX() of a timestamp loses its column type on SQLite, so the query
casts it to text and the repository parses either database's format;
checked on Postgres against the same SQL by hand. Tests: use case
Success / NotFound / Unauthorized, repository counts with a second
tenant's finding on the same asset id, isolation decision registered.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
The inventory as the mockup lays it out:
- Title with the asset and critical counts. Agent coverage is shown as
  "non mesurée": no agent reports per-asset inventory yet (#910), so
  the figure is signalled as unavailable rather than invented (D-069 E).
- "Voir la topologie" and "Lancer une découverte". Discovery runs a real
  scan: one enabled source starts it and the toast follows the job, several
  ask which, none leads to Infrastructure to configure one. Shown only to
  members with scanner:scan.
- Search and type pills with counts.
- One dense table: name with its hostname, resource id or address, type
  with its icon, criticality, owner, location (location attributes),
  linked risks, open vulnerabilities with a KEV tag, last detection.
  Vulnerability columns come from /assets/exposure and are only fetched
  for members who may read vulnerabilities; others see a dash that says
  why.
- A row opens the asset drawer.

Kept outside the mockup: the typed attribute search (behind a toggle),
selection with governed bulk delete, criticality filter, column chooser
and export, edit / history / delete per row; creation, import and
attribute schemas move to the "more" menu. Score and category columns
stay available, hidden by default. All copy moves to fr/en keys.

DataTable gains an optional searchClassName so a page can give the search
box a fixed basis; the default is unchanged everywhere else.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
#906)

The owner found the type pills above the table badly arranged, and a
critique agreed: eleven equal pills over two lines, nine of them saying
"1", then a third line of table buttons, all before the first row.

Filtering now uses the design-system tabs on the schema's categories
(Serveurs, Postes, Applications, Bases de données, Réseau, Cloud,
Fournisseurs, Données, Non classés), only those present, with counts,
keyboard navigation and the product's keyline. Free-text types stay in
the Type column. The toolbar is one line at 1440 and 1280: search, a
"KEV seulement" toggle with its count (only for members who may read
vulnerabilities), the attribute search as an icon button, filters,
columns and export.

The DataTable searchClassName prop added for the pills has no use left
and is removed.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
Base automatically changed from feat/905-activity to master October 12, 2026 07:42
@alex-dembele alex-dembele reopened this Oct 12, 2026
@alex-dembele
alex-dembele merged commit a1cef49 into master Oct 12, 2026
14 of 26 checks passed
@alex-dembele
alex-dembele deleted the feat/906-assets branch October 12, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(frontend): asset inventory on the redesign layout

1 participant