Skip to content

Hypershell-112 UI with adjustments - #214

Open
kdoberst wants to merge 19 commits into
openshift-online:mainfrom
kdoberst:Hypershell-112-ui-adjustments
Open

Hypershell-112 UI with adjustments#214
kdoberst wants to merge 19 commits into
openshift-online:mainfrom
kdoberst:Hypershell-112-ui-adjustments

Conversation

@kdoberst

Copy link
Copy Markdown
Collaborator

Summary

  • Introduces an operational dashboard in the web console with a new @openshift-online/hypershell-operational-dashboard-ui package (widgetized layout, utilization charts, summary metrics).
  • Wires the dashboard into the app shell and navigation, with i18n for dashboard strings.
  • Restricts dashboard access to users with hypershell-admins or platform:admin realm roles.

Changes

  • New packages/operational-dashboard-ui package with dashboard page, charts, and mock metrics fixture.
  • Web console route, shell nav entry, BFF role plumbing, and admin gate (RequireDashboardAdmin).
  • Storybook story and unit tests for the admin gate and role helpers.

Metrics are served from a mock control plane adapter for now; live API integration can follow in a later PR.

Screenshot 2026-08-27 at 9 31 51 AM

Test plan

  • pnpm --filter @openshift-online/hypershell-operational-dashboard-ui check
  • pnpm --filter @openshift-online/hypershell-web-console test:run (or the web-console unit test command you normally use)
  • make kind-web-console-up — and go to: https://console.hypershell.localhost/dashboard OR run story book: cd components/web-console && pnpm run storybook
  • Confirm dashboard labels render correctly (no missing i18n keys)

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a4fb86e-5fc8-43e8-bed0-8e3bc5f11d46

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@jsell-rh

jsell-rh commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Amber review

Status: Complete

Verdict

COMMENT — the operational-dashboard feature is well-structured (clean hexagonal boundary, domain probes, correlation IDs, thorough component/CI registration, additive tests) and I found no blocker. However, this PR overlaps almost entirely with PR #209 and takes a dashboard-plumbing design that conflicts with PR #211, so maintainers must decide which PR owns this feature before merge. A stray unrelated planning file and an authorization-claim behavior change also warrant attention.

Hi team — Amber here. This is a large, mostly-new web-console feature adding an operational dashboard behind a packages/operational-dashboard-ui package, an admin gate (client + BFF), and full CI/registration wiring. Overall quality is good; my concerns are concentrated in cross-PR coordination, one accidental file, and authorization-claim semantics.

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

COMMENT — the operational-dashboard feature is well-structured (clean hexagonal boundary, domain probes, correlation IDs, thorough component/CI registration, additive tests) and I found no blocker. However, this PR overlaps almost entirely with PR #209 and takes a dashboard-plumbing design that conflicts with PR #211, so maintainers must decide which PR owns this feature before merge. A stray unrelated planning file and an authorization-claim behavior change also warrant attention.

Hi team — Amber here. This is a large, mostly-new web-console feature adding an operational dashboard behind a packages/operational-dashboard-ui package, an admin gate (client + BFF), and full CI/registration wiring. Overall quality is good; my concerns are concentrated in cross-PR coordination, one accidental file, and authorization-claim semantics.

Cross-PR coordination

I compared this PR against all other open PRs in openshift-online/hypershell. Open PRs at review time: #73, #75, #109, #135, #148, #150, #151, #179, #182, #185, #188, #189, #194, #200, #201, #206, #207, #208, #209, #210, #211, #212, #216.

Two material conflicts need a maintainer decision:

  1. PR #209 "HYPERSHELL-112 Dashboard UI" — duplicate solution. #209 and this PR (#214 "Hypershell-112 UI with adjustments") implement the same HYPERSHELL-112 feature. The changed-file lists are near-identical (same packages/operational-dashboard-ui package, same web-console shell/route/BFF/role wiring, same i18n and CI registration). #214 is a superset — it adds gateway-status-chart.tsx, gateway-exception-status-counts.ts, metric-trend-change.ts, and an error-state Storybook story. Decision needed: these cannot both merge. Maintainers should pick #214 as the successor and close #209 (or explicitly rebase one onto the other), otherwise the second to merge will collide on every shared file.

  2. PR #211 "fix(kind): expose gateway metrics and fix control plane connectivity" — incompatible dashboard data-plumbing design. Both PRs create components/web-console/app/composition/dashboard-composition.ts as a new file with divergent, incompatible contents:

    • This PR: dashboardOperations = createDashboardOperations({ controlPlane: createMockDashboardControlPlane() }), importing createDashboardOperations from the new @openshift-online/hypershell-operational-dashboard-ui package and a mock adapter under app/adapters/mock/.
    • PR #211: defines its own local DashboardOperations interface and wires a live adapter createDashboardControlPlaneAdapter() from app/adapters/api/dashboard-control-plane.ts, backed by Prometheus + a gateway-management-ui metrics dashboard — it does not use the new operational-dashboard-ui package at all.
      This is not a mere text merge conflict: the two PRs assume different owning package, different composition interface, and different data source (mock vs live Prometheus). This PR's body says "live API integration can follow in a later PR," which suggests #211 is meant to supply the live data — but as written they disagree on the composition contract and adapter location. Decision needed: agree on the canonical dashboard composition/adapter contract and a merge order (which package owns the dashboard; whether #211 layers its live adapter onto #214's createDashboardOperations port rather than replacing the composition).

No material conflict was found with the other open PRs (dependency bumps #73/#75/#135/#188/#189; spec/docs #148/#151/#185/#200; control-plane/API #179/#182/#194/#201/#207/#216; CLI #206; e2e #212; security tooling #109). PR #208/#210 touch gateway-management-ui gateway-connection files but do not overlap this PR's changes.

Findings

Major

  • Stray unrelated file packages/gateway-management-ui/plan.md (187 lines). Its content is an implementation plan for "Update Daily Note - Jira" (branch 004-daily-jira-items, .agents/skills/, Jira REST API) — it has nothing to do with HyperShell or this feature and appears to be an accidental commit. It lands inside the shared gateway-management-ui package (included in that component's lint/path scope). Please delete it. Confidence: High.

  • extractRealmRoles now falls back to the groups claim for authorization (bff/src/auth.ts:69-79). The admin gate matches exact literals hypershell-admins / platform:admin. Keycloak group memberships are commonly emitted as paths with a leading slash (e.g. /hypershell-admins), which would silently fail to match, and conflating group names with realm-role names for a privilege decision can also over-grant if group and role namespaces differ. Since this feeds the dashboard admin gate, please confirm the exact Keycloak claim/format this targets and normalize (or scope the fallback) so the authorization decision is deterministic. Confidence: Medium.

Minor

  • Client-side admin gate falls through for unauthenticated/undefined sessions (app/features/dashboard/require-dashboard-admin.tsx:32). The deny branch only triggers when session?.authenticated is true; an unauthenticated or failed-load session returns children. Server-side BFF enforcement covers the real boundary, so this is defense-in-depth only, but consider an explicit not-authorized/redirect for the non-authenticated case rather than rendering the dashboard shell. Confidence: High.

  • domain-probes package public types entry repointed from dist/*.d.ts to src/*.ts (components/web-console/domain-probes/package.json:8,11,16). This changes type resolution for every consumer of a shared package and is bundled into a feature PR. It's likely intentional (so the new package type-checks against source without a prior build), but call it out explicitly and confirm it doesn't break published-artifact consumers or build ordering. Confidence: Medium.

  • Admin gate is only enforced when config.oidcIssuer is set (bff/src/app.ts:379). In no-auth dev mode the dashboard is open to everyone — acceptable for local dev, but worth a comment so it isn't mistaken for production behavior. Confidence: High.

What looks good

  • New package respects the narrow hexagonal boundary: control-plane access is behind a DashboardControlPlane port, workflow effects go through a DashboardProbePublisher fan-out, correlation IDs are generated per invocation, and there are no raw console.*/telemetry calls in the package source.
  • Component registration is complete and consistent: component-paths.json, lint.yml (job + aggregate gate), pnpm-workspace.yaml, root package.json build/check scripts, swap-component.sh, and CLAUDE.md all updated together.
  • Test changes are additive — no pre-existing assertion was weakened or flipped. New BFF tests cover non-admin redirect and admin allow for both roles; the existing unauthenticated-redirect test was extended (not rewritten) to include /dashboard.

Findings Summary (ordered by severity, highest first):

  1. [Major] Cross-PR: duplicate of #209 and incompatible dashboard composition vs #211 — needs maintainer decision — Cross-PR Coordination
  2. [Major] Stray unrelated packages/gateway-management-ui/plan.md accidentally committed — Repo Hygiene
  3. [Major] extractRealmRoles groups-claim fallback may misfire authorization (Keycloak group path format) — Security / AuthZ (auth.ts L69)
  4. [Minor] Client admin gate falls through for unauthenticated sessions — Defense in Depth (require-dashboard-admin.tsx L32)
  5. [Minor] domain-probes types repointed to source .ts — shared-package contract change — API Surface (package.json L8)
  6. [Minor] Dashboard admin gate bypassed when OIDC disabled (dev mode) — Observability/Docs (app.ts L379)

Convention Checklist (only evaluated rows):

Convention Result
No panic() / no raw console.* in production code Pass
No secrets in logs or responses Pass
Input validated (auth roles/claims) Fail
Narrow hexagonal UI boundary (ports for external effects) Pass
Domain probes for UI observability Pass
Reuse PatternFly / no duplicate UI components Pass
Register every component in CI Pass
Image references consistent across manifests N/A
Test Diff Scrutiny (no weakened assertions) Pass

Comment thread packages/gateway-management-ui/plan.md Outdated
Comment thread components/web-console/bff/src/auth.ts Outdated
Comment thread components/web-console/domain-probes/package.json
Comment thread components/web-console/bff/src/app.ts
@kdoberst
kdoberst force-pushed the Hypershell-112-ui-adjustments branch from a4ecf2d to 8393fd2 Compare August 27, 2026 19:15
@kdoberst
kdoberst marked this pull request as ready for review August 27, 2026 19:24
@jsell-rh

jsell-rh commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Amber review

Status: Complete

Verdict

COMMENT — This is a well-structured, hexagonally-boundaried operational dashboard package with solid BFF+client admin gating and good test coverage; the concerns are around shipping mock data as if it were live, a dev-only hardcoded hostname, and a few observability/brand nits. Nothing here rises to a blocker, but the mock-data and hostname items warrant a maintainer decision before this becomes visible to admins.

The new @openshift-online/hypershell-operational-dashboard-ui package is cleanly layered (application ports, probes, presentation), CI registration is complete (component-paths, lint.yml, aggregate gate, Dockerfile, .dockerignore, workspace), and the admin gate is enforced in both the BFF (/dashboard + dashboard. host) and the client (RequireDashboardAdmin) with additive, well-targeted tests. Test diff scrutiny passed: the modified assertions in session-adapter.test.ts and bff/test/auth.test.ts are additive (new authEnabled field, new admin-gate cases) and do not flip or delete any prior guarantee.

Findings

[Major] Mock metrics are wired as the production dependency.
components/web-console/app/composition/dashboard-composition.ts injects createMockDashboardControlPlane() as the real controlPlane, so authenticated admins are shown fabricated numbers (with an artificial 2s delay) presented as live operational metrics. The PR body acknowledges this is interim, but there is no visible "sample data" indicator or feature flag. Admins could make operational judgments on fake data. Recommend gating the route behind a feature flag or rendering an unmistakable "sample data" banner until the live adapter lands.

[Major] Dev-only hardcoded hostname in home.tsx diverges from the BFF host model.
components/web-console/app/routes/home.tsx:22 decides whether / renders the dashboard via globalThis.location.hostname === "dashboard.hypershell.localhost", while the BFF (bff/src/app.ts) uses a generic dashboard. prefix (isDashboardHost). In any non-Kind environment (e.g. dashboard.<prod-domain>) the SPA at / on the dashboard host will render the Gateways page instead of the dashboard, so client behavior silently disagrees with the BFF gate. This violates "Separate configuration from code." Derive the check from the same dashboard. prefix logic or from runtime config rather than a literal localhost.

[Minor] Raw adapter error message rendered in the UI.
packages/operational-dashboard-ui/src/pages/operational-dashboard-page.tsx:472 and :482 render metricsQuery.error.message directly into the Alert. Once a live control-plane adapter exists, this can surface internal/backend detail to the browser and is unlocalized. Prefer the localized loadErrorBody/refreshErrorBody for display and route the raw message to a probe/log.

[Minor] Declared dashboard.layout.template.invalid probe is never emitted.
operational-dashboard-page.tsx:322 silently falls back to the default template when isValidSavedTemplate fails (and the surrounding catch also swallows parse errors), yet the probe contract declares dashboard.layout.template.invalid. Emit that probe on the invalid/parse-failure paths so the declared observability signal is actually produced, or drop the unused probe name.

[Minor] Hardcoded hex color literals in the status chart.
packages/operational-dashboard-ui/src/dashboard/gateway-status-chart.tsx:20-25 hardcodes hex values. They do map to approved palette swatches and correctly avoid using red for failure (failure uses danger-orange), which is good — but UI-BRAND-02 asks for named design tokens and flags hard-coded duplicates. Prefer PatternFly chart theme tokens over literal hex.

Cross-PR coordination

No material cross-PR coordination issue requires maintainer action.

Findings Summary (ordered by severity, highest first)

  1. [Major] Mock control-plane adapter wired as the production dependency; admins see fabricated metrics without a "sample data" indicator - Trust / Product (dashboard-composition.ts L6, mock/dashboard-control-plane.ts L12)
  2. [Major] Hardcoded dashboard.hypershell.localhost in home.tsx diverges from the BFF's generic dashboard. host model; dashboard won't render on / in non-Kind envs - Config-from-code (home.tsx L22)
  3. [Minor] Raw, unlocalized error.message rendered in dashboard Alerts - Observability / i18n (operational-dashboard-page.tsx L472, L482)
  4. [Minor] Declared dashboard.layout.template.invalid probe never emitted on the invalid/parse-failure path - Observability (operational-dashboard-page.tsx L322)
  5. [Minor] Hardcoded hex chart colors instead of named design tokens - Brand color (gateway-status-chart.tsx L20-25)

Convention Checklist

Convention Result
No panic() / no raw console in production code Pass
Errors handled, not silently swallowed Pass (probe-backed)
No secrets in logs or responses Pass
Restricted admin access enforced server-side (BFF) and client-side Pass
Test Diff Scrutiny (no flipped/removed guarantees) Pass
Component registered in CI (paths, lint job, aggregate, Docker) Pass
Separate configuration from code Fail (hardcoded dashboard hostname)
Narrow hexagonal UI boundary (ports for effects) Pass
Domain probes for observability (no raw telemetry) Pass
PatternFly reuse / named brand tokens Fail (hardcoded hex)
i18n for user-facing strings Partial (raw error.message unlocalized)

@jsell-rh jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

COMMENT — This is a well-structured, hexagonally-boundaried operational dashboard package with solid BFF+client admin gating and good test coverage; the concerns are around shipping mock data as if it were live, a dev-only hardcoded hostname, and a few observability/brand nits. Nothing here rises to a blocker, but the mock-data and hostname items warrant a maintainer decision before this becomes visible to admins.

The new @openshift-online/hypershell-operational-dashboard-ui package is cleanly layered (application ports, probes, presentation), CI registration is complete (component-paths, lint.yml, aggregate gate, Dockerfile, .dockerignore, workspace), and the admin gate is enforced in both the BFF (/dashboard + dashboard. host) and the client (RequireDashboardAdmin) with additive, well-targeted tests. Test diff scrutiny passed: the modified assertions in session-adapter.test.ts and bff/test/auth.test.ts are additive (new authEnabled field, new admin-gate cases) and do not flip or delete any prior guarantee.

Findings

[Major] Mock metrics are wired as the production dependency.
components/web-console/app/composition/dashboard-composition.ts injects createMockDashboardControlPlane() as the real controlPlane, so authenticated admins are shown fabricated numbers (with an artificial 2s delay) presented as live operational metrics. The PR body acknowledges this is interim, but there is no visible "sample data" indicator or feature flag. Admins could make operational judgments on fake data. Recommend gating the route behind a feature flag or rendering an unmistakable "sample data" banner until the live adapter lands.

[Major] Dev-only hardcoded hostname in home.tsx diverges from the BFF host model.
components/web-console/app/routes/home.tsx:22 decides whether / renders the dashboard via globalThis.location.hostname === "dashboard.hypershell.localhost", while the BFF (bff/src/app.ts) uses a generic dashboard. prefix (isDashboardHost). In any non-Kind environment (e.g. dashboard.<prod-domain>) the SPA at / on the dashboard host will render the Gateways page instead of the dashboard, so client behavior silently disagrees with the BFF gate. This violates "Separate configuration from code." Derive the check from the same dashboard. prefix logic or from runtime config rather than a literal localhost.

[Minor] Raw adapter error message rendered in the UI.
packages/operational-dashboard-ui/src/pages/operational-dashboard-page.tsx:472 and :482 render metricsQuery.error.message directly into the Alert. Once a live control-plane adapter exists, this can surface internal/backend detail to the browser and is unlocalized. Prefer the localized loadErrorBody/refreshErrorBody for display and route the raw message to a probe/log.

[Minor] Declared dashboard.layout.template.invalid probe is never emitted.
operational-dashboard-page.tsx:322 silently falls back to the default template when isValidSavedTemplate fails (and the surrounding catch also swallows parse errors), yet the probe contract declares dashboard.layout.template.invalid. Emit that probe on the invalid/parse-failure paths so the declared observability signal is actually produced, or drop the unused probe name.

[Minor] Hardcoded hex color literals in the status chart.
packages/operational-dashboard-ui/src/dashboard/gateway-status-chart.tsx:20-25 hardcodes hex values. They do map to approved palette swatches and correctly avoid using red for failure (failure uses danger-orange), which is good — but UI-BRAND-02 asks for named design tokens and flags hard-coded duplicates. Prefer PatternFly chart theme tokens over literal hex.

Cross-PR coordination

No material cross-PR coordination issue requires maintainer action.

Findings Summary (ordered by severity, highest first)

  1. [Major] Mock control-plane adapter wired as the production dependency; admins see fabricated metrics without a "sample data" indicator - Trust / Product (dashboard-composition.ts L6, mock/dashboard-control-plane.ts L12)
  2. [Major] Hardcoded dashboard.hypershell.localhost in home.tsx diverges from the BFF's generic dashboard. host model; dashboard won't render on / in non-Kind envs - Config-from-code (home.tsx L22)
  3. [Minor] Raw, unlocalized error.message rendered in dashboard Alerts - Observability / i18n (operational-dashboard-page.tsx L472, L482)
  4. [Minor] Declared dashboard.layout.template.invalid probe never emitted on the invalid/parse-failure path - Observability (operational-dashboard-page.tsx L322)
  5. [Minor] Hardcoded hex chart colors instead of named design tokens - Brand color (gateway-status-chart.tsx L20-25)

Convention Checklist

Convention Result
No panic() / no raw console in production code Pass
Errors handled, not silently swallowed Pass (probe-backed)
No secrets in logs or responses Pass
Restricted admin access enforced server-side (BFF) and client-side Pass
Test Diff Scrutiny (no flipped/removed guarantees) Pass
Component registered in CI (paths, lint job, aggregate, Docker) Pass
Separate configuration from code Fail (hardcoded dashboard hostname)
Narrow hexagonal UI boundary (ports for effects) Pass
Domain probes for observability (no raw telemetry) Pass
PatternFly reuse / named brand tokens Fail (hardcoded hex)
i18n for user-facing strings Partial (raw error.message unlocalized)

import { createMockDashboardControlPlane } from "../adapters/mock/dashboard-control-plane";

export const dashboardOperations = createDashboardOperations({
controlPlane: createMockDashboardControlPlane(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] The mock control-plane adapter is wired here as the production dependency, so authenticated admins are shown fabricated metrics (see the artificial 2s delay in mock/dashboard-control-plane.ts) presented as live data. The PR body notes this is interim, but there's no visible "sample data" indicator or feature flag. Consider gating the route behind a flag or rendering an unmistakable sample-data banner until the live adapter lands, so admins don't act on fake numbers.

const navigate = useNavigate();
const [searchParameters, setSearchParameters] = useSearchParams();
const hostname = globalThis.location.hostname;
if (hostname === "dashboard.hypershell.localhost") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Major] This hardcodes the Kind-only hostname dashboard.hypershell.localhost, while the BFF (bff/src/app.ts isDashboardHost) uses a generic dashboard. prefix. In any non-Kind environment (e.g. dashboard.<prod-domain>) the SPA at / on the dashboard host will render the Gateways page instead of the dashboard, so client behavior silently disagrees with the BFF gate. This violates "Separate configuration from code" - derive the check from the same dashboard. prefix logic or runtime config.

variant="danger"
>
{metricsQuery.error instanceof Error
? metricsQuery.error.message

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] Rendering metricsQuery.error.message verbatim (here and at ~L482) can surface internal/backend detail to the browser once a live adapter exists, and it's unlocalized. Prefer the localized loadErrorBody/refreshErrorBody for display and route the raw message to a probe/log.

}

const parsed = JSON.parse(rawTemplate) as ExtendedTemplateConfig;
if (!isValidSavedTemplate(parsed)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] When a saved template is invalid this falls back to default silently (and the surrounding catch also swallows parse errors), yet the probe contract declares dashboard.layout.template.invalid. Emit that probe on the invalid/parse-failure paths so the declared observability signal is actually produced, or drop the unused probe name.

*
* @see https://www.patternfly.org/components/alert
* @see https://www.patternfly.org/components/label
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Minor] These hex literals map to approved palette swatches and correctly avoid red for failure (uses danger-orange), which is good - but UI-BRAND-02 asks for named design tokens and flags hard-coded duplicates. Prefer PatternFly chart theme tokens over literal hex.

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.

2 participants