frontend: migrate to Vue 3 + Vite + TypeScript + Carbon Web Components, redesign visualizer - #336
frontend: migrate to Vue 3 + Vite + TypeScript + Carbon Web Components, redesign visualizer#336anoncam wants to merge 14 commits into
Conversation
Documents the side-by-side Vue 2.7 -> Vue 3 migration strategy: Vite + TypeScript + Pinia + Vue Router 4 + @carbon/web-components + @carbon/charts core. Plan covers PR sequencing (scaffold -> services -> visualizer redesign -> cutover), Carbon component mapping, risk register, and verification steps per PR. Visualizer redesign with interactive, responsive charts is the motivating goal and lands in PR #3. Signed-off-by: Cameron Banowsky <[email protected]>
…ration)
Scaffold the next-gen frontend at frontend-next/, runnable side-by-side
with the legacy Vue 2.7 app. No feature parity yet -- this PR proves
the toolchain end to end.
Stack
-----
- Vue 3.5 + Vite 8 + TypeScript 6 (strict)
- Pinia 3, Vue Router 5 (Vue 3 line)
- @carbon/web-components 2.54 (replaces @carbon/vue@2)
- @carbon/charts 1.27 (vanilla core, no Vue wrapper)
- @carbon/styles 1.106 + @carbon/icons 11.80
- ESLint 10 flat config + Prettier 3
Deliverables
------------
- App shell: main.ts bootstraps Pinia + Router, App.vue applies the
preserved make-the-carbon-theme-go-{white,dark} class names so the
legacy theming pattern carries over. Theme state lives in
stores/app.ts; OS preference is detected via matchMedia.
- HeaderBar.vue ported to cds-header / cds-header-name /
cds-header-global-action with a Pinia-bound 3-state theme toggle
(auto/light/dark). A small CarbonIcon.vue helper renders icons from
@carbon/icons descriptor objects.
- FooterView.vue ported to a plain footer using Carbon CSS custom
properties; dropped the @carbon/ibmdotcom-web-components dependency.
- CarbonChart.vue: generic Vue 3 wrapper around @carbon/charts core
classes (donut, treemap, bar variants, circle-pack, area, line, etc.).
Mounts in onMounted, calls model.setData on data change,
model.setOptions on options/theme change, destroys in onBeforeUnmount.
- HomeView renders a placeholder donut through the wrapper to prove the
pipeline. ResultsView is an empty placeholder for PR #3.
Build & runtime
---------------
- Multi-stage Dockerfile builds inside the image (node:22-alpine) so
the host needs no Node toolchain. Placeholder tokens
(__CBOMKIT_HTTP_API_BASE__, __CBOMKIT_WS_API_BASE__,
__CBOMKIT_TITLE__, __CBOMKIT_VIEWER_ONLY__, __CBOMKIT_POLICY_NAME__)
are baked into the bundle via build-time CBOMKIT_* env vars.
- docker/entrypoint.sh maps the existing VUE_APP_* env var contract
(still used by docker-compose.yaml) onto those placeholders at
container start, so swapping images at cutover requires no compose
changes. Glob updated from js/app.*.js to assets/**.{js,html,css}
to match Vite's hashed asset filenames.
- nginx config copied verbatim from the legacy frontend.
- Makefile gains additive dev-frontend-next and
build-frontend-next-image targets; existing frontend targets and
docker-compose.yaml are untouched.
Verification
------------
- npm install succeeds with 0 vulnerabilities (Sonatype-vetted
versions).
- npm run build (vue-tsc --noEmit && vite build) passes type check and
produces a working bundle.
- npm run dev serves on http://localhost:8002 alongside the legacy
frontend on :8001 with no port conflict.
- grep -c "__CBOMKIT_TITLE__" dist/** confirms the placeholder tokens
survive minification, so runtime sed substitution will work as
designed.
Out of scope for this PR
------------------------
Service layer (api.js, scan.js, model.js -> Pinia stores), TS types
for the CBOM schema, the visualizer redesign, scan/upload pages,
and the legacy-to-new cutover. Those land in PRs #3/#4/#5 per
frontend/MIGRATION.md.
Signed-off-by: Cameron Banowsky <[email protected]>
Ports the legacy frontend's model and helpers to TypeScript modules under frontend-next/src/. No UI changes yet -- legacy frontend still owns the user-facing experience until PR #3. Pinia stores (split by responsibility) -------------------------------------- - stores/errors.ts: ErrorEntry list + addError/closeError/clear. - stores/scan.ts: scanning state, codeOrigin, credentials, reset helpers (resetScanningInfo/CodeOriginInfo/Credentials). - stores/cbom.ts: cbom, dependency maps, policyCheckResult, lastCboms, showResults; startAgain() composes resets across scan + cbom stores. - stores/app.ts: theme state (unchanged from PR #1). Services -------- - services/api.ts: typed fetchLastCboms + getComplianceReport with the same fallback-to-local-compliance contract. - services/scan.ts: typed WebSocket client. Replaces the legacy uuid4 dep with crypto.randomUUID() and a small Math.random fallback. Message dispatch uses a discriminated union over ScanMessageType; payload parsing for DETECTION/CBOM funnels into setCbom + buildDependencyMaps. Pure helpers ------------ - lib/cbom.ts: checkCbomValidity, resolvePath, buildDependencyMaps, getDependenciesFor, setCbom, showResultFromApi/Upload, getDetections{,FromCbom}. - lib/compliance.ts: getComplianceLevel/Object/Color/Icon/Label/ Description, getPolicyResultsByAsset, hasValidComplianceResults type guard, checkValidComplianceResults validator, getComplianceRepartition/getColorScale. - lib/compliance-local.ts: createLocalComplianceReport (NIST PQC quantum-safe whitelist mirror of the legacy implementation). - lib/info.ts: getTermFullName/Description from the copied crypto-dictionary.json; countOccurrences / countNames over typed CbomComponent arrays. - lib/general.ts: capitalizeFirstLetter, numberFormatter, formatSeconds, openGitRepo, canOpenOnline, getCodeLink/openOnline (now take CodeOrigin as an arg so they remain pure), limitString. Types ----- - types/cbom.ts: Partial CycloneDX 1.6 + cryptography-ext schema covering the fields the frontend reads: AlgorithmProperties, CertificateProperties, ProtocolProperties, RelatedCryptoMaterial- Properties, CryptoProperties, EvidenceOccurrence, CbomComponent, CbomDependency, Cbom, ScanRecord, DependencyMaps, DependencyComponentList. - types/scan.ts: ScanState const-object enum, ScanMessageType union, ScanMessageEnvelope, ScanCredentials/Input, ScanRequestBody, CodeOrigin. - types/compliance.ts: ComplianceIconKey, ComplianceLevel, ComplianceFinding, PolicyCheckResultOk/Error (discriminated on `error`). - types/errors.ts: ErrorStatus const-object enum + ErrorEntry. Config ------ - config.ts replaces app.config.js. Composes API URLs from CBOMKIT_HTTP_API_BASE + CBOMKIT_WS_API_BASE via api.json. Empty base falls back to relative URLs so the Vite dev proxy works without env vars set locally. getTitle() / isViewerOnly() live here too. - api.json copied from frontend/. - data/crypto-dictionary.json copied from frontend/resources/. MIGRATION.md ------------ Reflects the renumbered sequence (PR #2 is now services/state, PR #3 is the visualizer, PR #4 is scan flow + cutover) and the updated PR #2 scope (no router changes -- routes get expanded in PR #3 alongside the visualizer ports). Verification ------------ - npm run type-check (vue-tsc --noEmit) passes with strict mode + verbatimModuleSyntax enabled. - npm run build succeeds; new modules tree-shake out for now (bundle hashes unchanged from PR #1) since no UI references them yet -- they enter the bundle in PR #3 when components start importing. Signed-off-by: Cameron Banowsky <[email protected]>
Ports the visualizer UI to Vue 3 and ships the chart improvements that
motivated the whole migration. Drag-drop or click "Try the sample CBOM"
on the home page to land on the new /results page.
Charts
------
Five charts in a responsive CSS-grid (no fixed pixel heights anymore):
- Compliance donut (with local-approximation indicator)
- Asset names circle pack
- Primitives donut
- Functions donut
- Primitive -> algorithm treemap (new chart type, full-width)
Each chart has the Carbon toolbar enabled (zoom, expand, export PNG/CSV).
The wrapper resolves chart-specific click events into a single
`datum-click` emit so parents don't have to learn each chart's event name.
Carbon Charts wraps donut datums as `{data: {group, value}}` and treemap
leaves as `{name, value}` -- a small `unwrap()` helper in StatisticsView
handles both shapes uniformly.
KPI strip
---------
Above the charts, four metric cards summarise at-a-glance:
crypto-asset count, quantum-vulnerable count, top primitive, top
function. The "vulnerable" card flips between positive (green) and
warn (amber) accents based on the count.
Drill-down
----------
Clicking a donut slice, a circle-pack node, or a treemap leaf emits a
`filter-change` event that the ResultsView routes into a filter prop on
the DataTable. A dismissible filter chip appears above the table; the
chip's X clears the filter. Clicking the same slice twice toggles the
filter off.
DataTable
---------
A simplified port (versus the 467-LOC legacy version): renders
detections from the cbom store with columns Status / Name / Primitive
/ Functions / Location. Free-text search input alongside the filter
chip. Empty-state row when filters yield no matches. Modal triggers
and column-level customisation are deferred to PR #4 (defaults are
enough for the visualizer redesign).
Component inventory
-------------------
New under src/components/results/:
- ComplianceIcon.vue: resolves the compliance icon descriptor through
complianceIconMap and applies the level colour.
Substitutes WatsonHealthImageAvailabilityUnavailable
(no longer in @carbon/icons) with the `unknown`
icon.
- KpiStrip.vue: the four metric cards.
- StatisticsView.vue: the charts grid + drill-down dispatch.
- DataTable.vue: the table with filter + search.
- ResultTitle.vue: title, subtitle (live detection count) + code-origin
tags (gitUrl/revision/commit/subfolder).
- ReturnButton.vue: stops the WebSocket, calls store.startAgain(),
navigates back to home.
New under src/components/home/:
- FileUploader.vue: plain drag-drop + click-to-browse JSON upload.
No Carbon Web Component used (the legacy
cv-file-uploader's slot API didn't translate
cleanly). Reads the file via FileReader, calls
showResultFromUpload + getComplianceReport,
emits an `uploaded` event the parent uses to
push to /results.
Other changes
-------------
- main.ts side-effect imports for cds-tile, tag, link, loading,
inline-loading, skeleton-text, file-uploader, data-table, search,
tooltip (registers them as custom elements).
- HomeView replaces the PR #1 placeholder donut with the uploader and
"Try the sample CBOM" button. The bundled sample is a copy of
example/keycloak-cbom.json -- yields 103 detections.
- Router: beforeEnter guard on /results redirects to home if no CBOM
is loaded.
- App.vue sets document.title = getTitle() at startup so the dev tab
title reads "CBOMkit" instead of the runtime placeholder token.
- types/filter.ts: shared DetectionFilter discriminated union, used by
StatisticsView, ResultsView, and DataTable.
Verification
------------
- npm run type-check (strict vue-tsc) passes.
- npm run build succeeds. Bundle: ResultsView 438 kB (gzip 131 kB),
index 1.27 MB (gzip 185 kB). Code-splitting per chart type can come
later; the warning is noise for the redesign milestone.
- Manual: dropped the bundled keycloak sample on the new UI in
Chrome; all 5 charts rendered, KPI strip showed (103 / 27 / Pke /
Keygen), clicking the compliance "Unknown" slice filtered the table
from 103 to 50 rows and surfaced a "Compliance: Unknown" chip; dark
mode matched OS preference.
- Legacy `make production` flow still works unchanged.
Out of scope for this PR
------------------------
- Scan flow (SearchBar, WebSocket UI, GitInfoPrompt).
- Modal subcomponents (CryptoAssetDetails, DependenciesView,
GithubEmbed).
- Notifications surface for errors.
- Vitest smoke tests + the legacy-to-new cutover.
All deferred to PR #4.
Signed-off-by: Cameron Banowsky <[email protected]>
Ports the remaining UI to reach parity with the legacy Vue 2 frontend, plus a Vitest smoke suite. The next commit performs the legacy cutover. Components ---------- - global/NotificationsView.vue: toast notification surface bound to the errors Pinia store. Maps the ErrorStatus enum to friendly title + description + kind (error / warning / info) and uses cds-toast-notification (registered in main.ts). - home/SearchBar.vue: Git URL / PURL input with optional advanced options (Scan tab: branch + subfolder; Authentication tab: username + password/PAT). Builds a credentials object and calls connectAndScan from services/scan, then routes to /results. Plain HTML inputs rather than cds-text-input -- the v-model story for custom-element inputs is fiddly and not worth a wrapper for this PR. - home/ListTable.vue: last CBOMs fetched via fetchLastCboms in onBeforeMount. Skeleton-text rows while loading. Clicking a row calls showResultFromApi + getComplianceReport and routes to /results. - home/SearchOrUploadView.vue: container that pairs SearchBar + FileUploader (with ListTable above when not in viewer-only mode). Replaces the bare HomeView layout. - results/LoaderView.vue: cds-inline-loading bound to the scan store's scanningStatus state machine (LOADING/ENDING/LOADED/ERROR). Wire-up ------- - App.vue mounts NotificationsView next to the header. - HomeView delegates to SearchOrUploadView and keeps the sample-CBOM shortcut. - ResultsView shows LoaderView in the toolbar while a scan is in progress. - Router guard on /results allows entry when a scan is in flight (live detections render) in addition to the previous "cbom is loaded" case. - main.ts registers additional cds-* tags: notification, checkbox, tabs, text-input. Tests (new) ----------- - vitest 4 + jsdom 29 + @vue/test-utils 2 set up via vite.config.ts. Imported from vitest/config so the `test` block type-checks. - src/lib/__tests__/compliance.test.ts: 9 cases covering checkValidComplianceResults validator, hasValidComplianceResults type guard, getComplianceLevel min-finding logic + default fallback. - src/lib/__tests__/compliance-local.test.ts: 5 cases over the NIST PQC local report -- RSA flagged Not Quantum Safe, ML-KEM hits the whitelist, symmetric primitives are Not Applicable, missing primitive is Unknown. - src/lib/__tests__/cbom.test.ts: 9 cases over resolvePath, getDetectionsFromCbom occurrence unwrap, buildDependencyMaps for both top-level dependencies and crypto-property refs. - src/lib/__tests__/info.test.ts: 4 cases over countOccurrences (string + array values) and countNames. All 27 tests pass. type-check + build remain clean. Out of scope (deferred to follow-up issues, not blockers for cutover) -------------------------------------------------------------------- - Modal subcomponents (CryptoAssetDetails, DependenciesView, GithubEmbed, GitInfoPrompt for post-upload git info). - RegulatorResults compliance scorecard (KPI strip already shows the vulnerable count and the StatisticsView donut covers the distribution). - ExplainerView / PluginExplainerView informational cards. - DebugView (dev-only). Signed-off-by: Cameron Banowsky <[email protected]>
Renames frontend-next/ to frontend/ and removes the legacy Vue 2 code.
Vue 3 + Vite + TypeScript + Pinia + Vue Router + @carbon/web-components
+ @carbon/charts is now the production frontend.
Changes
-------
- git mv frontend-next/ -> frontend/.
- git rm -rf frontend/ (legacy contents moved to frontend-legacy/ then
deleted; git rename detection keeps the per-file history through
the move).
- Makefile: the dev-frontend-next + build-frontend-next-image targets
are removed (only one frontend now). build-frontend-image points at
the multi-stage Vite Dockerfile. Added dev-frontend-local for `npm
run dev` without docker.
- frontend/MIGRATION.md is retained in git history; the migration is
complete.
User-visible
------------
- `make build-frontend-image` now produces an image built from the
Vue 3 source. Compose `make production` / `make dev-frontend` /
`make coeus` flows are unchanged from the outside; they continue
to pull `ghcr.io/cbomkit/cbomkit-frontend:${VERSION}` until the
user publishes a new image.
- `cd frontend && npm install && npm run dev` for local dev on :8002
(Vite proxies /api and /v1/scan to the Quarkus backend on :8081).
- The same VUE_APP_HTTP_API_BASE / VUE_APP_WS_API_BASE /
VUE_APP_TITLE / VUE_APP_VIEWER_ONLY / VUE_APP_POLICY_NAME env vars
are honoured by the nginx entrypoint (mapped to __CBOMKIT_*__
placeholder tokens at build time), so docker-compose.yaml requires
no changes.
Verification
------------
- npm install, npm run type-check, npm run build, npm test all pass
in the new frontend/ location (27 tests).
- Manual smoke in Chrome: home page, sample CBOM load, charts +
drill-down, notifications surface, search bar, list table skeleton
all render correctly and have no console errors.
- `make build-frontend-image` builds a working image via the
multi-stage Dockerfile.
Out of scope, follow-up issues
------------------------------
- Asset-detail modal, dependencies-graph modal, GitHub embed modal,
post-upload Git info prompt.
- Per-chart-type dynamic imports to shrink the main bundle from its
current ~1.97 MB (gzip 258 kB) baseline.
- Wire Vitest into CI.
Signed-off-by: Cameron Banowsky <[email protected]>
Three small bugs surfaced when running the new frontend against a real
Quarkus backend.
WebSocket URL synthesis
-----------------------
joinURL fallback returned a relative URL ("/v1/scan/...") when no
CBOMKIT_WS_API_BASE was set. fetch() resolves relative URLs against
the document base, but `new WebSocket()` requires an absolute URL --
so scans failed with close code 1006 before the upgrade was even
sent.
Split into joinHttpURL (relative is fine) and joinWsURL (synthesises
ws://<host>:<port>/... from window.location when no base is
configured).
Dev proxy CORS Origin rewrite
-----------------------------
The backend's allowlist is set via CBOMKIT_FRONTEND_URL_CORS, which
docker-compose pins to http://localhost:8001 (legacy frontend port).
The Vite dev server runs on :8002, so HTTP requests appeared to work
(Vite proxy hides origin from the browser) but the WebSocket upgrade
forwarded the browser's Origin header and the backend rejected it.
Both proxy entries now send Origin: http://localhost:8001 explicitly,
matching the allowlist. No change in production -- there the
frontend container talks to the backend on the docker network with
the right origin already.
Compliance trigger on scan completion
-------------------------------------
The legacy frontend ran the compliance check via a watcher on
model.cbom. PR #2 moved cbom into a Pinia store but the equivalent
trigger never landed, so post-scan CBOMs stayed at "Evaluating
compliance..." indefinitely.
services/scan.ts now calls getComplianceReport(cbom) inside the
CBOM message handler. The upload path already did this directly in
FileUploader, so both flows now produce a policy result.
Pre-set scan state for route guard
----------------------------------
connectAndScan sets isScanning + scanningStatus to LOADING
synchronously before opening the WebSocket. Without this, the
synchronous router.push('/results') hit the route guard before the
async onopen fired, and the guard sent the user back to home.
Verification
------------
- Manual: pkg:maven/io.quarkus/[email protected] scanned to
completion via the new UI; live progress messages streamed
("Cloning... Receiving objects 25% -> 90%, Resolving deltas 45%..."
-> "Scan finished"); 4 cryptographic assets detected; KPI strip
populated (Top primitive Hash, Top function Digest); commit hash
44bc4a9 and subfolder core/runtime auto-populated from backend
messages; donut + circle-pack charts rendered with real data.
- 27/27 Vitest tests still pass, type-check clean, build clean.
Signed-off-by: Cameron Banowsky <[email protected]>
- Replace .github/img/cbomkit.gif (1.7 MB, 14 frames) with a recording of the new visualizer: load the sample CBOM, click the Compliance donut "Not Quantum Safe" slice (table filters 103 -> 27, chip appears), clear the filter, click the largest treemap leaf (table filters by name 'key', 103 -> 24), scroll back to the chart grid. - Annotate the README image with a one-line caption naming what the GIF demonstrates. - Add a "Visualizer features" subsection under "Frontend and CBOMkit-coeus" describing the KPI strip, the five charts, drill-down behaviour, toolbar, responsive grid, and theme toggle. Notes the stack underneath (Vue 3 + Vite + TS + Pinia + Carbon). - Replace the stale CONTRIBUTING snippet that ran `vue-cli-service serve --port 8001` with the current `cd frontend && npm install && npm run dev` flow (Vite on :8002, proxies /api and /v1/scan to :8081, Origin rewrite for backend CORS). Add the type-check / test / build / lint shortcuts. Signed-off-by: Cameron Banowsky <[email protected]>
New Rego policy `distrusted_ca` in the existing `policies` package that flags CycloneDX cryptographic-asset certificates whose issuerName matches a known-distrusted or malicious Certificate Authority. Uses case-insensitive substring matching against issuer DNs (more robust than equality given RDN ordering/quoting variation across CBOM emitters). Initial distrusted list covers root-store removals from 2011-2024: DigiNotar, WoSign/StartCom, Symantec (incl. Thawte/GeoTrust/RapidSSL), Camerfirma, TrustCor, e-Tugra, Entrust. Result vocabulary: distrusted / trusted / unknown. Finding shape matches the existing OPAFinding contract (rule, result, value, bom-ref, property). Includes upload script mirroring upload_quantum_safe.sh, a sample input fixture covering all three outcomes, and a README documenting both policies and the local development workflow. Signed-off-by: Cameron Banowsky <[email protected]>
Adds DISTRUSTED and TRUSTED entries to the OPAResult enum and the ComplianceLevel map so findings emitted by the distrusted_ca Rego policy deserialize cleanly and surface through the compliance API and frontend without falling back to UNKNOWN. Severity ranks: 0 DISTRUSTED red, ERROR icon (uncompliant) 1 QUANTUM_VULNERABLE yellow, WARNING (uncompliant, unchanged) 2 UNKNOWN blue, UNKNOWN (uncompliant, unchanged) 3 QUANTUM_SAFE green, SECURE (compliant, unchanged) 4 NA gray, NA (compliant, unchanged) 5 TRUSTED green, CHECKMARK (compliant) Additive only; existing quantum_safe policy results are unaffected. Signed-off-by: Cameron Banowsky <[email protected]>
The Vue 3 migration upgraded Vite to ^8.0.13, which requires Node.js 20.19+ or 22.12+. Node 21 is past EOL and breaks Rolldown's native binding resolution with: Cannot find module '@rolldown/binding-linux-x64-gnu' Failing CI run prior to this fix: https://github.com/anoncam/cbomkit/actions/runs/25937520262 Bumping the matrix to 22.x (the next LTS line) restores green CI on every PR. No code or lockfile changes required. Signed-off-by: Cameron Banowsky <[email protected]>
|
Hi @anoncam - Many thanks for this PR. This is simply amazing and very, very valuable going forward. Two humble remarks,
|
|
@san-zrl I got you and will make those adjustments. |
… modals Addresses maintainer feedback on PR cbomkit#336: 1. Single-row visualizer layout - Drop the wide primitive -> algorithm treemap card. The treemap was the only widget on a second row and the maintainer flagged it as taking too much real estate. Its two info dimensions (primitive distribution and algorithm distribution) are already covered by the existing "Primitives" donut and "Asset names" circle pack, so removing it loses no information while collapsing the grid to one row. - Tighten the grid's column min-width from 280px to 240px so the four remaining cards fit comfortably across one row inside the 1200px app-main container, while still wrapping gracefully on narrow viewports. - Pin .charts__card to a fixed height of 380px with overflow:hidden. Without this, Carbon Charts' fullscreen toolbar action could leave the SVG with an oversized intrinsic size after exit, dragging the whole card vertically with the chart stuck at the top and legend pushed out of view. - Drop the now-unused unwrap-for-treemap path in the click handler and the .charts__card--wide / .charts__body--tall style rules. 2. Asset-detail modal - Re-introduce the legacy CryptoAssetDetails surface (deferred from PR #4 part 1) in components/results/modal/. Built on cds-modal + cds-structured-list web components so we stay on the new Vue 3 + Carbon Web Components stack. - DataTable rows are now keyboard-focusable buttons (role=button, tabindex=0, Enter/Space activated) that emit select-asset; ResultsView hosts the modal envelope and tracks the selected asset. - The modal surfaces the same four sections as before: code preview (GithubEmbed below), compliance finding + per-finding messages in a structured list, dependency graph (depends-on / provides-to / used-by / provided-by, each linking to a sub-detail view), and the full ordered CycloneDX 1.6 cryptographic-asset specification table. - Sub-asset navigation uses an in-modal stack: clicking a related asset's "See details" pushes a new frame; a "<- Back" affordance in the modal header pops back to the original selection. Closing the modal clears the stack. 3. GitHub-embed modal - Port the legacy GithubEmbed (deferred from PR #4 part 1) to consume getCodeLink + useScanStore.codeOrigin, swap the legacy emgithub theme selector to read useAppStore.useDarkMode, and keep the line-of-interest highlight via the same :nth-child(5) global rule. - Renders inline inside the asset-detail modal. Shows a placeholder copy when the CBOM lacks code-location metadata or the repository isn't GitHub. 4. Vue 3 component wiring - Register cds-modal and cds-structured-list custom elements in main.ts so the new modal's template renders without ad-hoc imports. Verification - npm run type-check, npm test (27/27), and npm run build all pass. - Drove a real keycloak-cbom.json scan against the running Quarkus backend in the dev server (vite :8002 -> :8081): * All four widgets render in a single row with their legends below. * Clicking the Compliance donut's "Not Quantum Safe" slice filters the asset table 103 -> 27 and surfaces a dismissible filter chip (drill-down preserved across the layout change). * Clicking an asset row opens the modal with code preview (live emgithub.com embed against BCEcdhEsAlgorithmProvider.java:208), compliance finding, and full specification. * Clicking a row with "Related Crypto Material" surfaces the Depends-on graph; pushing into a sub-asset (DSA) shows its own code preview, compliance, and "Is used by" reverse edges; Back pops the stack and the original asset re-renders. * Entering Carbon Charts fullscreen on the Compliance donut and exiting now leaves the card layout untouched (was: card stretched to ~1600px tall with legend lost). Signed-off-by: Cameron Banowsky <[email protected]>
|
@san-zrl pushed 1. Single-row visualizer (no more second-row real-estate hog). Dropped the wide primitive→algorithm treemap rather than reshaping it — its two info dimensions are already covered by the existing Primitives donut and Asset names circle-pack, so removing it loses no information and lets the four remaining widgets sit in a single tightened-grid row. Cards are now pinned at 2. Asset-detail modal + GitHub-embed restored. New Verification against the live Quarkus backend (keycloak sample, 103 detections):
Out-of-scope follow-ups from the original PR description still apply (dependencies-graph modal, post-upload Git-info prompt, per-chart-type dynamic imports for bundle shrink). |
… row repeat(auto-fit, minmax(240px, 1fr)) treats each Carbon Charts cell's min-content as the column's lower bound, and Carbon Charts' legend (the horizontal row of swatches + labels below the donut) routinely measures wider than 240px. On narrower-than-1200px result viewports that pushed the fourth card onto a second row, defeating the single-row layout the maintainer asked for. Switch to repeat(4, minmax(0, 1fr)) so the columns ignore intrinsic content width and stay at four equal fractions of the container. The existing overflow:hidden on .charts__card absorbs any legend that would otherwise overflow horizontally at very narrow widths. Add a single media-query break at <=720px that drops to two columns, where four would be unreadably small. Signed-off-by: Cameron Banowsky <[email protected]>
The `Build Frontend` workflow was red on both 072787b and 0b3551c with: Cannot find module '@rolldown/binding-linux-arm64-musl' npm has a bug related to optional dependencies (npm/cli#4828) Two compounding root causes: 1. No `frontend/.dockerignore`. The `COPY . .` step after `npm install` was clobbering the freshly resolved linux-<arch>-musl `node_modules/` with whatever sat on the build host — on a Mac, that's the arm64-darwin tree, which obviously has no linux-musl Rolldown binding. Add a `.dockerignore` excluding `node_modules`, `dist`, caches, and `.git`. 2. `npm ci` inside the multi-arch image build also fails because the committed lockfile only lists the binding for the platform the lockfile was generated on (npm cli #4828, which Rolldown's runtime error explicitly cites). Rolldown maintainers' instruction is to delete the lockfile and run a fresh install. Switch the build-stage install to: rm -f package-lock.json && npm install --include=dev \ --no-audit --no-fund --no-progress The pinned versions in package.json still give us a deterministic build for non-native deps; the lockfile stays the source of truth for `npm ci` in CI matrix runs (which only ever build for one platform). `--include=dev` is required because the image sets NODE_ENV=production at the top, which would otherwise omit vue-tsc and vite themselves. Verified locally with `docker buildx build --platform linux/arm64` and `--platform linux/amd64` — both produce a working image (the `npm run build` step exits 0, the Stage 2 nginx-unprivileged image exports cleanly). Signed-off-by: Cameron Banowsky <[email protected]>
TL;DR
Two related changes bundled into one PR:
@carbon/vue@2+@carbon/charts-vue@1(all EOL or stale) to Vue 3.5 + Vite 8 + TypeScript 6 (strict) + Pinia 3 + Vue Router 4 +@carbon/[email protected]+@carbon/[email protected]core. Same look-and-feel, redesigned visualizer (responsive grid, KPI strip, click-to-drill-down, new treemap), zero changes required todocker-compose.yamlor the helm chart. 27 Vitest smoke tests. End-to-end verified against the running Quarkus backend.policies.distrusted_caplus two newOPAResultlevels (DISTRUSTED/TRUSTED) so X.509 certificates issued by root-store-removed CAs (DigiNotar, WoSign/StartCom, Symantec, Camerfirma, TrustCor, e-Tugra, Entrust) are flagged at compliance time and surface through the redesigned visualizer's drill-down. Additive — existingquantum_safeevaluations are unchanged.All commits are DCO-signed.
Demo (also embedded in the updated README):
Why this needs to happen
The current frontend stack has multiple EOL or unmaintained dependencies that block any meaningful UI work:
vue@carbon/vue@carbon/charts-vue@carbon/[email protected]API. Current Carbon Charts is on a 1.27.x line with a different surface.@vue/cli-servicevue-template-compilervue-simple-progresseslinteslint-plugin-vueThere is no in-place upgrade path. Vue 2's compat build cannot host
@carbon/vue@2under Vue 3 — Carbon hard-coded Vue 2 APIs in its components. The IBM-supported successor for Vue 3 is@carbon/web-components(framework-agnostic custom elements). Adopting that is what unlocks any future UI work.The distrusted-CA work piggybacks on this PR because (a) it's the same author's stack of branches and (b) the new visualizer's drill-down is what actually makes the
DISTRUSTEDfinding category usable — clicking the new compliance result type filters the asset table to just the affected certificates.What's in this PR
11 commits on this branch. Squash-friendly if maintainers prefer a single commit on this side.
Frontend migration
98 files changed, +10,473 / −40,684 (most of the deletions are the pre-compiled
carbon-both.cssbundle which is now provided by@carbon/stylesSCSS).verbatimModuleSyntax)reactive()blob inmodel.jsapp/cbom/scan/errorsstores/results@carbon/vue@2(Vue-2-only)@carbon/[email protected](Carbon's framework-agnostic line)@carbon/charts-vue@1wrapper@carbon/[email protected]core wrapped in a thin Vue 3 componentcarbon-both.css@carbon/[email protected]SCSS, themed via Carbon design tokens@carbon/icons-vue@10@carbon/[email protected](SVG descriptors rendered via a small wrapper)All deps Sonatype-vetted;
npm installreports 0 vulnerabilities.Distrusted CA compliance policy
New Rego policy in
opa/distrusted_ca.regounder the existingpoliciespackage. Matches CycloneDX cryptographic-asset certificates whoseissuerName(case-insensitive substring) appears on a curated list of root-store removals from 2011–2024:Result vocabulary:
distrusted/trusted/unknown. Finding shape matches the existingOPAFindingcontract (rule, result, value, bom-ref, property).OPAResult.javaandComplianceLevelextended additively so the new findings deserialize cleanly and the frontend doesn't fall back toUNKNOWN:DISTRUSTEDQUANTUM_VULNERABLEUNKNOWNQUANTUM_SAFENATRUSTEDShips with an upload script (
opa/upload_distrusted_ca.sh, mirroringupload_quantum_safe.sh), a sample input fixture (opa/testdata/distrusted_ca_input.json) covering all three outcomes, and anopa/README.mddocumenting both policies and the local OPA development workflow.CI
.github/workflows/frontend.ymlNode matrix bumped from 21.x → 22.x. Node 21 is past EOL and Vite 8's Rolldown bindings (@rolldown/binding-linux-x64-gnu) fail to resolve on it; 22.x is the next LTS line.Visualizer redesign — the user-facing reason
The legacy
StatisticsView.vuerenders four charts in fixed-width columns (20% each) with fixed pixel heights (320 / 230) andtoolbar: { enabled: false }everywhere — see the legacy source. The donuts are read-only and there's no summary card.The new visualizer keeps the same Carbon palette but addresses each of those constraints:
enabled: falseg100chart theme + tokenized surfaces; three-state toggle (auto / light / dark) in the header tracks OS preferenceThe GIF at the top shows the full flow: load sample → KPI populates (103/27/Pke/Keygen) → click Compliance "Not Quantum Safe" slice → table filters 103 → 27 with a chip → dismiss chip → click largest treemap leaf → table filters 103 → 24 by name.
The new
DISTRUSTED/TRUSTEDcompliance levels render in the same Compliance donut asQUANTUM_VULNERABLE/QUANTUM_SAFEetc., with the severity ranks above driving color and icon — and clicking aDISTRUSTEDslice filters the asset table to just the certificates whose issuer is on the distrust list.Backwards-compatibility — nothing breaks for operators
The deployment surface is identical. No changes are required in
docker-compose.yaml, the helm chart, or the runtime env contract:VUE_APP_HTTP_API_BASE,VUE_APP_WS_API_BASE,VUE_APP_TITLE,VUE_APP_VIEWER_ONLY,VUE_APP_POLICY_NAMEare all still honored. The newfrontend/docker/entrypoint.shreads them and substitutes the corresponding__CBOMKIT_*__placeholder tokens that were baked into the Vite bundle at build time.node:22-alpinebuild stage →nginxinc/nginx-unprivileged:alpineruntime), so no host Node toolchain is required formake build-frontend-image.make production,make coeus,make dev-frontendare untouched. They continue to pullghcr.io/cbomkit/cbomkit-frontend:${VERSION}until the maintainers publish a new image from this code.make build-frontend-imagenow produces a Vue 3 image; the legacy target's behavior is preserved.make dev-frontend-localruns the Vite dev server on:8002and proxies/apiand/v1/scanto the backend on:8081. The dev proxy rewrites theOriginheader to match the backend's CORS allowlist (CBOMKIT_FRONTEND_URL_CORS, defaulthttp://localhost:8001) so cross-origin scans work locally.distrusted_capolicy is opt-in — operators must upload it viaopa/upload_distrusted_ca.sh(or select it viaCBOMKIT_POLICY_NAME/VUE_APP_POLICY_NAME) for it to evaluate. Default behavior is unchanged for existing deployments.How it was built — side-by-side then swap
To minimize review burden and keep
make productionworking at every checkpoint, the frontend migration was built side-by-side atfrontend-next/, then swapped intofrontend/at the end. Each step was a separate sub-PR on the fork:frontend/MIGRATION.md(preserved in this PR's git history at commit1c8d9273d) locking in the target stack and risk register.<CarbonChart>wrapper, multi-stage Dockerfile.helpers/*andmodel.js. Pinia stores. CBOM / scan / compliance / errors / filter TS types.config.tsreplacingapp.config.js.git mv frontend-next/ → frontend/with the legacy code removed.Originrewrite for backend CORS, missing post-scan compliance trigger, router-guard race against the async WebSocket open.OPAResult/ComplianceLeveladditions to surface the findings through the API and the new visualizer.The commit history on this branch tells the story:
Every commit is DCO-signed. Squash-friendly if you prefer a single merge commit. Or merge as-is and bisect-friendly history is preserved.
Verification
npm run type-check—vue-tsc --noEmitpasses in strict mode withverbatimModuleSyntaxon.npm test— 27/27 Vitest cases pass coveringlib/compliance.ts,lib/compliance-local.ts,lib/cbom.ts(CBOM validation, occurrence unwrap, dependency map construction), andlib/info.ts. ~1 s wall-clock.npm run build— succeeds. Production bundle 1.97 MB / 258 kB gzip. (Per-chart-type dynamic imports are a follow-up to shrink further.)make build-frontend-image— produces a working image via the multi-stage Dockerfile.opa eval -d opa/distrusted_ca.rego -i opa/testdata/distrusted_ca_input.json 'data.policies.compliance'returns the expecteddistrusted/trusted/unknownmix for the three sample certificates (DigiNotar, Let's Encrypt, custom issuer).pkg:maven/io.quarkus/[email protected]via the new SearchBar.LABEL("Cloning git repository: Receiving objects 25% → 90%, Resolving deltas 45%…" → "Scan finished"),GITURL,BRANCH,REVISION_HASH(commit44bc4a9),FOLDER(core/runtime) messages — all auto-populated the result-title tags.Risks + how they're mitigated
frontend-next/was used so every interim commit leftmake productionfunctional.v-modelcv-*components were either drop-in tocds-*or hand-rolled when the slot composition didn't translate.assets/**/*.{js,html,css}with unique placeholder tokens (__CBOMKIT_HTTP_API_BASE__etc.) that can't collide with real code. TheVUE_APP_*contract ondocker-compose.yamlis preserved verbatim.@carbon/charts+d3. Per-chart-type dynamic imports are a follow-up that should claw most of this back. Not a blocker for the visual improvements.@carbon/iconsno longer exportsWatsonHealthImageAvailabilityUnavailable24unknownicon.distrusted_casset at the top ofopa/distrusted_ca.rego— updates are a one-line change. Substring (not equality) matching tolerates RDN ordering / quoting variation across CBOM emitters. False positives possible if a legitimate org embeds one of these names in its issuer DN — acceptable trade-off given the threat model.Conflicts with open Dependabot PRs
Closing or dropping these would be appropriate after merge:
frontend/package.jsonwhich is being replaced wholesale. None of those bumps apply.(All other open PRs are Java-side and unaffected.)
What I'd ask of reviewers
frontend/MIGRATION.mdat commit1c8d9273d(or the squashed equivalent) for the stack-choice rationale.cd frontend && npm install && npm run devand try the four interactions in the GIF.npm testandnpm run type-check— both should be green.docker-compose.yaml, the helm chart, or thefrontend-imageGitHub Actions workflow needs adjustment.opa/distrusted_ca.regoagainst the sample fixture and the curated CA list. The list is intentionally conservative (root-store removals only, not "looks suspicious"); additions should cite a public distrust announcement.Out of scope (intentional follow-ups, not blockers)
ghcr.io/cbomkit/cbomkit-frontend:${VERSION}image from this code somake productionexercises Vue 3 end-to-end out of the box.Test plan
cd frontend && npm install && npm run dev— home renders, sample loads, charts + drill-down work, no console errors.npm test— 27 pass.npm run type-check && npm run build— both pass.make build-frontend-image— image builds.example/{flick,kafka,keycloak}-cbom.json) onto the new UI — charts render, KPI numbers match donut totals.opa/upload_distrusted_ca.shagainst a local OPA — policy uploads cleanly.opa/testdata/distrusted_ca_input.json) — compliance result returnsDISTRUSTED, frontend renders the red ERROR icon, clicking the Compliance "Distrusted" slice filters the asset table to the affected certificates.