Skip to content

Hypershell-154 UI with adjustments - #214

Closed
kdoberst wants to merge 2 commits into
openshift-online:mainfrom
kdoberst:Hypershell-112-ui-adjustments
Closed

Hypershell-154 UI with adjustments#214
kdoberst wants to merge 2 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: Team

Run ID: 573b21c4-f8a4-43ef-9d8f-fd17038666de

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
@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.

maknop added a commit that referenced this pull request Sep 1, 2026
…us wiring

- Remove duplicate ServiceMonitor (deploy/base/servicemonitor.yaml);
  keep single canonical copy under deploy/base/prometheus/
- Replace 75k-line vendored prometheus-operator-bundle.yaml with an
  upstream URL reference; add to both infrastructure/ and
  infrastructure-no-cnpg/ kustomizations so all Kind DB modes work
- Wire Prometheus stack into kind-up: wait for prometheus-operator
  after infrastructure apply, then apply deploy/base/prometheus/ after
  the main component rollout (CRDs exist, namespace already present)
- Implement GET /api/hypershell/v1/metrics/gateways: add CountByPhase
  to GatewayService interface + sqlGatewayService, MetricsGateways
  handler, and auth-gated route; fix fetch URL in gateway-metrics-data.ts
- Mount GatewayMetricsDashboard on /metrics route in web-console:
  add route to route-contract.json, routes.ts, new metrics.tsx, and
  BFF isApplicationRoute; add i18n keys to locales/en.json
- Add seccompProfile: RuntimeDefault to Prometheus CR securityContext
- Remove dashboard-composition.ts and dashboard-control-plane.ts to
  avoid collision with PRs #209 and #214 which own that entry point
- Fix index-based JSON6902 patch in database-deployment component:
  --metrics-server-bindaddress shifted the serve command array by one,
  so the sslmode replace op index increments from 12 to 13

Co-Authored-By: Crush <crush@charm.land>
maknop added a commit that referenced this pull request Sep 1, 2026
…us wiring

- Remove duplicate ServiceMonitor (deploy/base/servicemonitor.yaml);
  keep single canonical copy under deploy/base/prometheus/
- Replace 75k-line vendored prometheus-operator-bundle.yaml with an
  upstream URL reference; add to both infrastructure/ and
  infrastructure-no-cnpg/ kustomizations so all Kind DB modes work
- Wire Prometheus stack into kind-up: wait for prometheus-operator
  after infrastructure apply, then apply deploy/base/prometheus/ after
  the main component rollout (CRDs exist, namespace already present)
- Implement GET /api/hypershell/v1/metrics/gateways: add CountByPhase
  to GatewayService interface + sqlGatewayService, MetricsGateways
  handler, and auth-gated route; fix fetch URL in gateway-metrics-data.ts
- Mount GatewayMetricsDashboard on /metrics route in web-console:
  add route to route-contract.json, routes.ts, new metrics.tsx, and
  BFF isApplicationRoute; add i18n keys to locales/en.json
- Add seccompProfile: RuntimeDefault to Prometheus CR securityContext
- Remove dashboard-composition.ts and dashboard-control-plane.ts to
  avoid collision with PRs #209 and #214 which own that entry point
- Fix index-based JSON6902 patch in database-deployment component:
  --metrics-server-bindaddress shifted the serve command array by one,
  so the sslmode replace op index increments from 12 to 13

Co-Authored-By: Crush <crush@charm.land>
Introduce the operational-dashboard-ui package and wire it into the web
console with gateway status, utilization, and summary widgets, layout
persistence, mock metrics support, and dashboard host routing.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kdoberst
kdoberst force-pushed the Hypershell-112-ui-adjustments branch from 8393fd2 to a4eaf4b Compare September 1, 2026 22:12
@jsell-rh

jsell-rh commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@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, well-tested feature PR that adds an operational dashboard as a new private React package and wires it into the web console with an admin-only access gate. The code follows the project's hexagonal-boundary and error-handling conventions cleanly; my findings are all Minor (observability wiring, a header-derived redirect, and duplicated security constants) with no blockers.

I reviewed the security-critical BFF auth/role changes, the admin gate, the new package's application layer, and the modified test files against the HyperShell security, control-plane, and review conventions.

What works well

  • Admin gate is defense-in-depth. Access is enforced server-side in the BFF (onRequest hook redirects non-admins away from /dashboard, and sendApplication re-checks) and again client-side via RequireDashboardAdmin. Enforcement is correctly gated on config.oidcIssuer so no-auth dev mode stays open, which is documented in-line.
  • extractRealmRoles is robust and well-tested. It handles the roles / groups / realm_access.roles precedence, normalizes leading-slash group paths, and ignores non-string entries, with matching unit tests.
  • Test diff scrutiny: clean. The only modified assertions (session-adapter.test.ts, bff/test/auth.test.ts) are strictly additive - they add the new authEnabled field and new admin/non-admin route cases. No pre-existing guarantee was flipped or removed.
  • Error handling / observability in the package. localStorage read/write and JSON.parse are guarded; parse/persist failures emit typed probes instead of throwing. The workflow layer publishes started/completed/cancelled/failed probes with correlation IDs.
  • CI + config registration is complete. New package registered in component-paths.json, a dedicated lint-operational-dashboard-ui job added and included in the aggregate gate, plus Dockerfile, .dockerignore, pnpm-workspace.yaml, CLAUDE.md, and i18n extraction globs all updated consistently.

Findings (all Minor)

  1. Dashboard domain probes are wired to the default no-op publisher in production composition (dashboard-composition.ts + DashboardUiProvider in application-shell.tsx), so the typed workflow/layout facts are dropped. The gateway composition wires a real fan-out publisher; the dashboard should follow. See inline comment.
  2. consoleRedirectForDashboardHost builds its redirect target from the client-controlled Host header. Low risk (self-affecting), but prefer trusted config or an allowlist. See inline comment.
  3. Admin role constants and helper are duplicated across the BFF and frontend with no drift protection. These are security-relevant; consider a shared module or a sync test. See inline comment.

Cross-PR coordination

No material cross-PR coordination issue requires maintainer action.

Findings Summary (ordered by severity, highest first):

  1. [Minor] Dashboard domain probes never wired into the fan-out publisher (defaults to no-op) - Observability / Convention (dashboard-composition.ts L5, application-shell.tsx L149)
  2. [Minor] Redirect target derived from client-controlled Host header - Security (app.ts L73)
  3. [Minor] Duplicated admin-role constants/helper across BFF and frontend with no drift guard - Maintainability / Security (roles.ts L1)

Convention Checklist:

Convention Result
No secrets in logs or responses Pass
Input validated (roles, host, JSON parse guarded) Pass
Server-side authorization enforced (admin gate) Pass
Error paths propagate / no silent swallowing Pass
Domain probes published through fan-out port Fail (defaults to no-op in production)
Test diff scrutiny (no flipped/removed guarantees) Pass
CI component registration complete Pass
Image/config references consistent across stack Pass
Conventional commit messages Pass


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

export const dashboardOperations = createDashboardOperations({

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] Dashboard domain probes are never wired into the fan-out publisher in production. createDashboardOperations is called with only controlPlane, so probes defaults to noopDashboardProbePublisher. The dashboard already emits typed workflow probes (dashboard.workflow.started/completed) and layout probes (dashboard.layout.template.*), but with a no-op publisher these facts are silently dropped in the browser. Compare gateway-composition.ts, which wires probes: gatewayObservability.probes into the fan-out port. CLAUDE.md's "Domain probes for UI observability" convention expects these facts to be published through the fan-out port. Since metrics are still mock data, this is Minor, but please wire the probe publisher (here and in the DashboardUiProvider below) before/with the live-API follow-up so dashboard observability isn't a no-op. Confidence: High.

<DashboardUiProvider
dashboard={dashboardOperations}
navigation={dashboardNavigation}
usesSampleData

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] DashboardUiProvider omits probes. The provider accepts an optional probes publisher but none is passed here, so any probe consumer resolved via useDashboardUi() gets undefined and dashboard observability stays a no-op in production. Pass the same fan-out publisher used for gateways (see gateway-composition.ts). Confidence: High.

return host.startsWith(DASHBOARD_HOST_PREFIX);
}

function consoleRedirectForDashboardHost(request: FastifyRequest): string {

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] Redirect target is derived from the client-controlled Host header. consoleRedirectForDashboardHost builds the Location from request.headers.host, swapping the dashboard. prefix for console.. Because it only reshapes the prefix and prepends console., this is low risk (a spoofed Host only affects the spoofing client), but it is still an externally-influenced redirect. Consider deriving the console host from trusted config rather than the request header, or validating the host against an allowlist. Confidence: Medium.

@@ -0,0 +1,14 @@
/** Keycloak realm role for HyperShell administrators. */

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] Role constants and hasDashboardAdminRole are duplicated between bff/src/roles.ts and app/lib/session-roles.ts (and isDashboardHost between bff/src/app.ts and app/lib/dashboard-host.ts). Duplication across the process boundary is understandable, but these are security-relevant constants (admin role names) that must not drift. Consider a shared workspace module or a test that asserts the two definitions stay in sync. Confidence: Medium.

Gate the operational dashboard behind hypershell-admins and platform:admin
roles in the BFF and browser, with access-denied UI and improved OIDC role
claim extraction for Keycloak group mappings.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kdoberst
kdoberst force-pushed the Hypershell-112-ui-adjustments branch from a4eaf4b to 5184bc0 Compare September 2, 2026 13:12
@jsell-rh

jsell-rh commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@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

This PR adds a well-structured, admin-gated operational dashboard with proper defense-in-depth (server-side role enforcement in the BFF plus a client-side gate), thorough additive tests, complete CI registration, and a clean hexagonal/probe architecture. No blockers or security regressions were found; the items below are minor, plus one cross-PR coordination point that needs a maintainer decision.

What's good

  • Access control is enforced server-side, not just in React. bff/src/app.ts gates /dashboard (and the dashboard-host root) in both the onRequest hook and sendApplication, and RequireDashboardAdmin adds a client-side gate. No-auth dev mode is handled consistently via the new authEnabled flag.
  • Test changes are additive and honest. The modified assertions in session-adapter.test.ts and auth.test.ts add the new authEnabled field / new dashboard cases rather than flipping an existing guarantee. The 404-vs-401 distinction in the session adapter (no-auth vs unauthenticated) is a genuine improvement and is covered.
  • CI and packaging are fully wired. .github/component-paths.json, lint.yml (new lint-operational-dashboard-ui job + aggregate gate), .dockerignore, Dockerfile, pnpm-workspace.yaml, i18n extraction globs, and CLAUDE.md were all updated together.

Minor findings

  1. [Minor] dashboard-control-plane.ts ships an artificial setTimeout(…, 2000) ("just for demos") in the composition path wired into the real app shell, and the delay is not abort-aware (only throwIfAborted() runs before it). Remove it or confine it to Storybook/fixtures. Observability/UX
  2. [Minor] Role/host logic is duplicated across the client/server boundary: bff/src/roles.ts and app/lib/session-roles.ts are byte-identical, and app/lib/dashboard-host.ts re-encodes the dashboard. prefix logic embedded in bff/src/app.ts. Duplication across module systems is acceptable, but the admin role names and host prefix are now defined in multiple places and can drift; consider a shared source or a cross-check test. Maintainability
  3. [Minor] operational-dashboard-ui/package.json depends on @patternfly/widgetized-dashboard@1.0.0-prerelease.6, a prerelease pulled into a shipped package. Confirm this is intentional and pinned; a prerelease can introduce breaking changes without a semver signal. Dependency risk
  4. [Minor / forward-looking] Server-side enforcement currently gates the HTML route and nav only; the /api/* proxy has no dashboard-admin check. That is fine today because metrics are mock/client-side, but when live metrics replace the mock adapter the backing endpoint must add server-side role enforcement so non-admins cannot fetch the data directly. Security (future)

Cross-PR coordination

A competing/overlapping dashboard effort needs a maintainer decision with #211. That PR introduces a GatewayMetricsDashboard (real Prometheus-backed pipeline) inside the existing gateway-management-ui package with a /metrics route, while this PR introduces a separate operational dashboard in a new operational-dashboard-ui package with a /dashboard route backed by mock data. Two decisions are required: (a) whether these are complementary surfaces or a duplicated dashboard direction, and specifically whether this PR's operational dashboard should consume #211's real metrics pipeline instead of the mock adapter rather than each building its own data path; and (b) the shared BFF route allowlist (isApplicationRoute in bff/src/app.ts), app/routes.ts, and route-contract.json are extended by both PRs, so whichever merges second must reconcile the allowlist to include both routes (a dropped entry causes the other route to 404/misroute) — this is an ordering/integration constraint, not just a text merge.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Artificial 2s (non-abort-aware) delay in the mock dashboard adapter on the production composition path - Observability/UX
  2. [Minor] Duplicated admin-role/host constants across BFF and app (drift risk) - Maintainability
  3. [Minor] Prerelease @patternfly/widgetized-dashboard dependency in a shipped package - Dependency risk
  4. [Minor] /api/* proxy lacks dashboard-admin enforcement for the eventual live metrics endpoint - Security (future)

Convention Checklist

Convention Result
No secrets in logs or responses Pass
Input validated (correlation id, role filtering) Pass
Register every component in CI Pass
Conventional commit messages Pass
Domain probes for UI observability (no raw console/telemetry) Pass
PatternFly 6 reuse / no duplicate UI primitives Pass
Narrow hexagonal UI boundary (ports for effects) Pass
Test Diff Scrutiny (modified assertions are additive, not removed guarantees) Pass

return {
async getOperationalMetrics(context: DashboardInvocationContext) {
context.signal?.throwIfAborted();

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.

This 2s artificial delay ("just for demos") sits in the composition wired into the real application shell, so it ships in the actual web console, not just Storybook. It is also not abort-aware: throwIfAborted() runs before the wait but a cancellation during the 2s is ignored, so a navigation-away still resolves the promise. Remove the delay or move this artificial latency into a Storybook/fixture-only adapter.

PLATFORM_ADMIN_ROLE,
]);

export function hasDashboardAdminRole(roles: readonly string[]): boolean {

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.

This file is byte-identical to bff/src/roles.ts, and the dashboard. host prefix is likewise defined both here-adjacent (dashboard-host.ts) and inline in bff/src/app.ts. Duplication across the client/server module boundary is understandable, but the admin role names and host prefix are now security-relevant constants defined in multiple places that can silently drift. Consider a single shared source or a test that asserts the two lists stay in sync.

"@patternfly/react-charts": "8.6.1",
"@patternfly/react-core": "6.6.0",
"@patternfly/react-icons": "6.6.0",
"@patternfly/widgetized-dashboard": "1.0.0-prerelease.6",

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.

@patternfly/widgetized-dashboard@1.0.0-prerelease.6 is a prerelease being pulled into a shipped package. Prereleases can ship breaking changes without a semver bump. Please confirm this is intentional and note the plan to move to a stable release before this dashboard graduates from mock data.

return;
}
if (
requiresDashboardAdminAccess(pathname, request.headers.host) &&

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.

Nice defense-in-depth on the HTML route here. Forward-looking note: enforcement currently covers only the application/HTML routes and nav; the /api/* proxy below has no dashboard-admin check. That is fine while metrics are mock/client-side, but when the mock adapter is replaced with a live metrics endpoint, the data path must add server-side role enforcement so a non-admin cannot fetch dashboard data directly by calling the API through the proxy.

rh-amarin pushed a commit to rh-amarin/hypershell that referenced this pull request Sep 3, 2026
…penshift-online#211)

* fix(kind): expose gateway metrics and fix control plane connectivity

- Add HTTPRoute for metrics.hypershell.localhost routing to the API
  server's dedicated metrics port (4433), making Prometheus-format
  gateway metrics browsable in the local Kind cluster

- Bind the metrics server to 0.0.0.0:4433 (was localhost-only) so the
  gateway can reach it from outside the pod

- Fix the controller NetworkPolicy to explicitly allow all egress;
  kindnet's implementation implicitly blocks all egress when any
  NetworkPolicy selects a pod, which prevented the control plane from
  reaching CoreDNS and caused gateways to remain in a blank phase

- Use fully-qualified service names for the control plane's gRPC and
  HTTP API server addresses in the Kind overlay to avoid DNS search
  domain ambiguity

- Print the metrics URL in the kind-up summary alongside the other
  service URLs

Co-Authored-By: Crush <crush@charm.land>

* feat(deploy): add ServiceMonitor for Prometheus scraping

Brings in the ServiceMonitor from openshift-online#202, wiring Prometheus Operator
scraping to the metrics port (4433) already exposed by the API server.

Co-Authored-By: Crush <crush@charm.land>

* fix(kind): rename metrics hostname to observability.hypershell.localhost

Co-Authored-By: Crush <crush@charm.land>

* fix(e2e): move ServiceMonitor out of base to avoid CRD-not-found failure

The ServiceMonitor was included in deploy/base/kustomization.yaml which
is applied unconditionally, causing kind-up to fail in environments
without Prometheus Operator installed (including CI).

Moves it into deploy/base/prometheus/ alongside the existing Prometheus
resources, where it is only applied by overlays that have Prometheus
Operator available.

Co-Authored-By: Crush <crush@charm.land>

* feat(metrics): add gateway metrics dashboard and Prometheus infrastructure

Introduces a gateway metrics dashboard in the management UI backed by a
Prometheus stack wired into the Kind dev cluster. Includes RBAC, scrape
config, the Prometheus operator bundle, a new metrics data layer, and
the platform spec describing the desired dashboard state.

Co-Authored-By: Crush <crush@charm.land>

* fix(ci): resolve lint and policy failures from metrics dashboard files

- Replace import from non-existent operational-dashboard-ui package
  with locally defined types in the web-console adapter, eliminating
  all unsafe-assignment and unsafe-call ESLint errors
- Export metrics symbols from the gateway-management-ui public index
  so the web-console adapter can resolve them with full type safety
- Replace em dashes in the metrics dashboard spec with hyphens to
  satisfy the repository forbidden-terms policy check

Co-Authored-By: Crush <crush@charm.land>

* fix(ci): apply Prettier formatting to dashboard control plane adapter

Co-Authored-By: Crush <crush@charm.land>

* fix(ci): sync i18n catalog with new gateway metrics dashboard messages

Co-Authored-By: Crush <crush@charm.land>

* fix(metrics): address review blockers on gateway metrics and Prometheus wiring

- Remove duplicate ServiceMonitor (deploy/base/servicemonitor.yaml);
  keep single canonical copy under deploy/base/prometheus/
- Replace 75k-line vendored prometheus-operator-bundle.yaml with an
  upstream URL reference; add to both infrastructure/ and
  infrastructure-no-cnpg/ kustomizations so all Kind DB modes work
- Wire Prometheus stack into kind-up: wait for prometheus-operator
  after infrastructure apply, then apply deploy/base/prometheus/ after
  the main component rollout (CRDs exist, namespace already present)
- Implement GET /api/hypershell/v1/metrics/gateways: add CountByPhase
  to GatewayService interface + sqlGatewayService, MetricsGateways
  handler, and auth-gated route; fix fetch URL in gateway-metrics-data.ts
- Mount GatewayMetricsDashboard on /metrics route in web-console:
  add route to route-contract.json, routes.ts, new metrics.tsx, and
  BFF isApplicationRoute; add i18n keys to locales/en.json
- Add seccompProfile: RuntimeDefault to Prometheus CR securityContext
- Remove dashboard-composition.ts and dashboard-control-plane.ts to
  avoid collision with PRs openshift-online#209 and openshift-online#214 which own that entry point
- Fix index-based JSON6902 patch in database-deployment component:
  --metrics-server-bindaddress shifted the serve command array by one,
  so the sslmode replace op index increments from 12 to 13

Co-Authored-By: Crush <crush@charm.land>

---------

Co-authored-by: Crush <crush@charm.land>
@kdoberst kdoberst changed the title Hypershell-112 UI with adjustments Hypershell-154 UI with adjustments Sep 3, 2026
@kdoberst kdoberst closed this Sep 3, 2026
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